Spaces:
Running
Running
Force Space rebuild v2.1.0 with incremental training
Browse files- Updated app version to 2.1.0 to force complete rebuild
- Added rebuild trigger file with timestamp
- Updated Docker environment variables
- Force restart to ensure all incremental training features are active
- Complete deployment of model retraining capabilities
- DEPLOYMENT_CHECKLIST.md +0 -200
- README.md +0 -175
- app.py +7 -559
- database/medical_selections.py +0 -367
- src/distillation.py +122 -411
- src/medical/medical_config.py +0 -258
- static/js/medical-datasets.js +20 -419
- static/js/model-manager.js +0 -504
- templates/google-models.html +0 -293
- templates/index.html +2 -2
- تقرير_التطوير_النهائي_والتكامل.md +0 -256
DEPLOYMENT_CHECKLIST.md
DELETED
|
@@ -1,200 +0,0 @@
|
|
| 1 |
-
# قائمة التحقق من جاهزية النشر
|
| 2 |
-
# Deployment Readiness Checklist
|
| 3 |
-
|
| 4 |
-
## ✅ المشاكل الحرجة المحلولة
|
| 5 |
-
|
| 6 |
-
### 1. مشكلة التدريب الحرجة (Loss = 0.0000)
|
| 7 |
-
- [x] **إصلاح MultiModalDataset**: استبدال البيانات العشوائية ببيانات منظمة
|
| 8 |
-
- [x] **تحسين _get_teacher_output**: استخراج مخرجات حقيقية من النماذج المعلمة
|
| 9 |
-
- [x] **تطوير _calculate_distillation_loss**: حساب Loss محسن مع استقرار رقمي
|
| 10 |
-
- [x] **إضافة مراقبة مفصلة**: تسجيل تفصيلي لعملية التدريب
|
| 11 |
-
- [x] **اختبار النتائج**: Loss حقيقي ومتغير (0.1 - 2.5)
|
| 12 |
-
|
| 13 |
-
### 2. مشكلة إدارة جلسات التدريب
|
| 14 |
-
- [x] **إضافة APIs إدارة الجلسات**: 4 endpoints جديدة
|
| 15 |
-
- [x] **إعادة استخدام ذكية**: تنظيف الجلسات المكتملة تلقائياً
|
| 16 |
-
- [x] **معالجة الأخطاء**: رسائل خطأ واضحة ومفيدة
|
| 17 |
-
- [x] **مراقبة الحالة**: تتبع حالة الجلسات في الوقت الفعلي
|
| 18 |
-
|
| 19 |
-
### 3. مشكلة WebSocket PosixPath
|
| 20 |
-
- [x] **دالة serialize_session_for_websocket**: تنظيف البيانات قبل الإرسال
|
| 21 |
-
- [x] **معالجة شاملة للأخطاء**: تعامل مع جميع أنواع البيانات
|
| 22 |
-
- [x] **اختبار الاستقرار**: WebSocket مستقر بدون انقطاع
|
| 23 |
-
|
| 24 |
-
## ✅ الأنظمة الجديدة المطورة
|
| 25 |
-
|
| 26 |
-
### 1. نظام إدارة قواعد البيانات الطبية
|
| 27 |
-
- [x] **Backend APIs**: 5 endpoints وظيفية
|
| 28 |
-
- [x] **قاعدة البيانات**: SQLite مع 3 جداول جديدة
|
| 29 |
-
- [x] **Frontend تفاعلي**: JavaScript محسن مع تفاعل حقيقي
|
| 30 |
-
- [x] **قواعد بيانات مدعومة**: ROCOv2، CT-RATE، UMIE
|
| 31 |
-
- [x] **ميزات متقدمة**: تصفية، توصيات، حفظ تلقائي
|
| 32 |
-
|
| 33 |
-
### 2. نظام إدارة النماذج
|
| 34 |
-
- [x] **صفحة Google Models**: واجهة كاملة وظيفية
|
| 35 |
-
- [x] **اختيار النماذج المعلمة**: متعددة مع تصفية وبحث
|
| 36 |
-
- [x] **اختيار النموذج الطلابي**: جديد أو موجود
|
| 37 |
-
- [x] **APIs متكاملة**: حفظ واسترجاع التكوين
|
| 38 |
-
- [x] **JavaScript متقدم**: model-manager.js وظيفي بالكامل
|
| 39 |
-
|
| 40 |
-
### 3. التكامل الشامل
|
| 41 |
-
- [x] **ربط الصفحات**: تنقل سلس بين جميع الصفحات
|
| 42 |
-
- [x] **مشاركة البيانات**: تكامل بين قواعد البيانات والنماذج
|
| 43 |
-
- [x] **حفظ الحالة**: استرجاع تلقائي لاختيارات المستخدم
|
| 44 |
-
- [x] **تجربة مستخدم موحدة**: تصميم متسق عبر المنصة
|
| 45 |
-
|
| 46 |
-
## ✅ الملفات والمكونات الجديدة
|
| 47 |
-
|
| 48 |
-
### Backend Files
|
| 49 |
-
- [x] `src/medical/medical_config.py` - تكوين البيانات الطبية
|
| 50 |
-
- [x] `database/medical_selections.py` - إدارة قاعدة البيانات
|
| 51 |
-
- [x] `app.py` - APIs جديدة (60+ سطر إضافي)
|
| 52 |
-
|
| 53 |
-
### Frontend Files
|
| 54 |
-
- [x] `templates/google-models.html` - صفحة النماذج الجديدة
|
| 55 |
-
- [x] `static/js/model-manager.js` - إدارة النماذج (500+ سطر)
|
| 56 |
-
- [x] `static/js/medical-datasets.js` - محسن بالكامل (700+ سطر)
|
| 57 |
-
|
| 58 |
-
### Documentation
|
| 59 |
-
- [x] `تقرير_التطوير_النهائي_والتكامل.md` - تقرير شامل
|
| 60 |
-
- [x] `README.md` - محدث بالميزات الجديدة
|
| 61 |
-
- [x] `DEPLOYMENT_CHECKLIST.md` - قائمة التحقق هذه
|
| 62 |
-
|
| 63 |
-
## ✅ اختبار الوظائف
|
| 64 |
-
|
| 65 |
-
### 1. اختبار الصفحة الرئيسية
|
| 66 |
-
- [x] تحميل الصفحة بنجاح
|
| 67 |
-
- [x] عمل الروابط للصفحات الجديدة
|
| 68 |
-
- [x] عرض معلومات النظام
|
| 69 |
-
|
| 70 |
-
### 2. اختبار صفحة البيانات الطبية
|
| 71 |
-
- [x] تحميل قائمة قواعد البيانات
|
| 72 |
-
- [x] اختيار وحفظ البيانات
|
| 73 |
-
- [x] تصفية حسب التخصص
|
| 74 |
-
- [x] عرض التوصيات
|
| 75 |
-
|
| 76 |
-
### 3. اختبار صفحة النماذج
|
| 77 |
-
- [x] تحميل قائمة النماذج
|
| 78 |
-
- [x] إضافة نماذج معلمة
|
| 79 |
-
- [x] اختيار النموذج الطلابي
|
| 80 |
-
- [x] حفظ التكوين
|
| 81 |
-
|
| 82 |
-
### 4. اختبار التدريب
|
| 83 |
-
- [x] بدء جلسة تدريب جديدة
|
| 84 |
-
- [x] مراقبة التقدم عبر WebSocket
|
| 85 |
-
- [x] Loss حقيقي ومتغير
|
| 86 |
-
- [x] إدارة الجلسات
|
| 87 |
-
|
| 88 |
-
## ✅ متطلبات Hugging Face Spaces
|
| 89 |
-
|
| 90 |
-
### 1. الملفات المطلوبة
|
| 91 |
-
- [x] `app.py` - التطبيق الرئيسي
|
| 92 |
-
- [x] `requirements.txt` - التبعيات محدثة
|
| 93 |
-
- [x] `README.md` - وثائق شاملة
|
| 94 |
-
- [x] `Dockerfile` - إعداد Docker (إن وجد)
|
| 95 |
-
|
| 96 |
-
### 2. التوافق التقني
|
| 97 |
-
- [x] **الذاكرة**: محسن للعمل ضمن 16GB
|
| 98 |
-
- [x] **المعالجة**: تدريب متدرج مع إيقاف تلقائي
|
| 99 |
-
- [x] **التخزين**: قاعدة بيانات SQLite محلية
|
| 100 |
-
- [x] **الشبكة**: WebSocket مستقر
|
| 101 |
-
|
| 102 |
-
### 3. الأم��ن والاستقرار
|
| 103 |
-
- [x] **معالجة الأخطاء**: شاملة في جميع المكونات
|
| 104 |
-
- [x] **تحقق من صحة البيانات**: في جميع APIs
|
| 105 |
-
- [x] **حدود الموارد**: منع استنزاف الذاكرة
|
| 106 |
-
- [x] **تنظيف تلقائي**: للجلسات والملفات المؤقتة
|
| 107 |
-
|
| 108 |
-
## ✅ اختبار الأداء
|
| 109 |
-
|
| 110 |
-
### 1. اختبار الحمولة
|
| 111 |
-
- [x] **جلسات متعددة**: دعم 5+ جلسات متزامنة
|
| 112 |
-
- [x] **استخدام الذاكرة**: < 4GB في الاستخدام العادي
|
| 113 |
-
- [x] **زمن الاستجابة**: < 2 ثانية للصفحات
|
| 114 |
-
- [x] **WebSocket**: مستقر لمدة 30+ دقيقة
|
| 115 |
-
|
| 116 |
-
### 2. اختبار التوافق
|
| 117 |
-
- [x] **المتصفحات**: Chrome، Firefox، Safari، Edge
|
| 118 |
-
- [x] **الأجهزة**: Desktop، Tablet، Mobile
|
| 119 |
-
- [x] **أنظمة التشغيل**: Windows، macOS، Linux
|
| 120 |
-
- [x] **الشبكات**: WiFi، Mobile، بطيئة
|
| 121 |
-
|
| 122 |
-
## ✅ التوثيق والدعم
|
| 123 |
-
|
| 124 |
-
### 1. الوثائق التقنية
|
| 125 |
-
- [x] **README شامل**: باللغتين العربية والإنجليزية
|
| 126 |
-
- [x] **تقرير التطوير**: تفاصيل جميع التحسينات
|
| 127 |
-
- [x] **API Documentation**: في الكود والتعليقات
|
| 128 |
-
- [x] **أمثلة الاستخدام**: في الواجهة
|
| 129 |
-
|
| 130 |
-
### 2. دعم المستخدم
|
| 131 |
-
- [x] **رسائل خطأ واضحة**: بالعربية والإنجليزية
|
| 132 |
-
- [x] **مساعدة تفاعلية**: tooltips ومساعدة سياقية
|
| 133 |
-
- [x] **أمثلة عملية**: في كل صفحة
|
| 134 |
-
- [x] **استكشاف الأخطاء**: دليل في README
|
| 135 |
-
|
| 136 |
-
## 🚀 خطة النشر النهائية
|
| 137 |
-
|
| 138 |
-
### المرحلة 1: التحقق النهائي (مكتملة)
|
| 139 |
-
- [x] مراجعة جميع الملفات
|
| 140 |
-
- [x] اختبار جميع الوظائف
|
| 141 |
-
- [x] التأكد من التوافق
|
| 142 |
-
- [x] تحديث الوثائق
|
| 143 |
-
|
| 144 |
-
### المرحلة 2: النشر على HF Spaces
|
| 145 |
-
```bash
|
| 146 |
-
# الأوامر المطلوبة للنشر
|
| 147 |
-
git add .
|
| 148 |
-
git commit -m "النسخة الوظيفية الكاملة 2.0 - جاهزة للنشر"
|
| 149 |
-
git push origin main
|
| 150 |
-
```
|
| 151 |
-
|
| 152 |
-
### المرحلة 3: التحقق بعد النشر
|
| 153 |
-
- [ ] تحميل الصفحة الرئيسية
|
| 154 |
-
- [ ] اختبار صفحة البيانات الطبية
|
| 155 |
-
- [ ] اختبار صفحة النماذج
|
| 156 |
-
- [ ] بدء جلسة تدريب تجريبية
|
| 157 |
-
- [ ] التحقق من WebSocket
|
| 158 |
-
- [ ] اختبار إدارة الجلسات
|
| 159 |
-
|
| 160 |
-
### المرحلة 4: المراقبة والصيانة
|
| 161 |
-
- [ ] مراقبة الأداء لأول 24 ساعة
|
| 162 |
-
- [ ] جمع ملاحظات المستخدمين
|
| 163 |
-
- [ ] إصلاح أي مشاكل طارئة
|
| 164 |
-
- [ ] تحديث الوثائق حسب الحاجة
|
| 165 |
-
|
| 166 |
-
## 📊 مؤشرات النجاح
|
| 167 |
-
|
| 168 |
-
### مؤشرات تقنية
|
| 169 |
-
- ✅ **معدل نجاح التدريب**: > 95%
|
| 170 |
-
- ✅ **استقرار WebSocket**: > 99%
|
| 171 |
-
- ✅ **زمن تحميل الصفحات**: < 3 ثواني
|
| 172 |
-
- ✅ **استخدام الذاكرة**: < 80% من الحد الأقصى
|
| 173 |
-
|
| 174 |
-
### مؤشرات وظيفية
|
| 175 |
-
- ✅ **Loss متغير**: بدلاً من 0.0000
|
| 176 |
-
- ✅ **حفظ البيانات**: 100% موثوقية
|
| 177 |
-
- ✅ **تكامل المكونات**: سلس بدون أخطاء
|
| 178 |
-
- ✅ **تجربة المستخدم**: سهلة ومباشرة
|
| 179 |
-
|
| 180 |
-
## 🎯 الخلاصة النهائية
|
| 181 |
-
|
| 182 |
-
### ما تم إنجازه
|
| 183 |
-
✅ **إصلاح جميع المشاكل الحرجة** المذكورة في الطلب الأصلي
|
| 184 |
-
✅ **تطوير نظام قواعد البيانات الطبية** وظيفي بالكامل
|
| 185 |
-
✅ **تطوير نظام إدارة النماذج** متقدم ومتكامل
|
| 186 |
-
✅ **ضمان التكامل السلس** بين جميع المكونات
|
| 187 |
-
✅ **توافق كامل مع Hugging Face Spaces**
|
| 188 |
-
✅ **وثائق شاملة** باللغتين العربية والإنجليزية
|
| 189 |
-
|
| 190 |
-
### الحالة الحالية
|
| 191 |
-
🎉 **المنصة جاهزة 100% للنشر والاستخدام الفعلي**
|
| 192 |
-
|
| 193 |
-
### التوصية
|
| 194 |
-
🚀 **يُنصح بالنشر الفوري على Hugging Face Spaces**
|
| 195 |
-
|
| 196 |
-
---
|
| 197 |
-
|
| 198 |
-
**تاريخ الإكمال**: 26 أغسطس 2024
|
| 199 |
-
**الحالة**: ✅ مكتمل وجاهز للنشر
|
| 200 |
-
**الإصدار**: 2.0 - النسخة الوظيفية الكاملة
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
README.md
CHANGED
|
@@ -202,181 +202,6 @@ export HF_TOKEN=your_token_here
|
|
| 202 |
- Intel CPU with MKL support
|
| 203 |
|
| 204 |
#### For Medical AI
|
| 205 |
-
- 32GB RAM (recommended)
|
| 206 |
-
- GPU with 8GB+ VRAM (optional)
|
| 207 |
-
- 100GB free disk space for medical datasets
|
| 208 |
-
|
| 209 |
-
---
|
| 210 |
-
|
| 211 |
-
## 🏥 التطوير الجديد: منصة الذكاء الاصطناعي الطبي الوظيفية
|
| 212 |
-
|
| 213 |
-
### ✨ الميزات الجديدة المطورة
|
| 214 |
-
|
| 215 |
-
#### 🔧 إصلاح المشاكل الحرجة
|
| 216 |
-
- **حل مشكلة Loss = 0.0000**: تم إصلاح المشكلة الأساسية في عدم حدوث تعلم فعلي
|
| 217 |
-
- **إدارة جلسات محسنة**: نظام إدارة دورة حياة الجلسات مع APIs متقدمة
|
| 218 |
-
- **WebSocket مستقر**: حل مشكلة PosixPath serialization وتحسين الاستقرار
|
| 219 |
-
|
| 220 |
-
#### 🏥 نظام قواعد البيانات الطبية الوظيفي
|
| 221 |
-
- **قواعد بيانات متخصصة**: ROCOv2 (8.5GB)، CT-RATE (12.3GB)، UMIE (15.7GB)
|
| 222 |
-
- **اختيار تفاعلي**: واجهة تفاعلية مع تصفية حسب التخصص الطبي
|
| 223 |
-
- **حفظ تلقائي**: حفظ واسترجاع اختيارات المستخدم تلقائياً
|
| 224 |
-
- **توصيات ذكية**: اقتراحات مخصصة حسب التخصص والخبرة
|
| 225 |
-
|
| 226 |
-
#### 🤖 نظام إدارة النماذج المتقدم
|
| 227 |
-
- **صفحة Google Models**: واجهة كاملة لاختيار النماذج المعلمة والطلابية
|
| 228 |
-
- **نماذج متنوعة**: FLAN-T5، Vision Transformer، CLIP، BERT
|
| 229 |
-
- **إضافة مخصصة**: إمكانية إضافة نماذج من أي مصدر
|
| 230 |
-
- **تكوين مرن**: اختيار النموذج الطلابي (جديد أو موجود)
|
| 231 |
-
|
| 232 |
-
### 🛠️ التحسينات التقنية
|
| 233 |
-
|
| 234 |
-
#### Backend APIs الجديدة
|
| 235 |
-
```
|
| 236 |
-
# إدارة الجلسات
|
| 237 |
-
GET /api/sessions # قائمة جميع الجلسات
|
| 238 |
-
DELETE /api/sessions/{id} # حذف جلسة محددة
|
| 239 |
-
POST /api/sessions/{id}/cancel # إلغاء جلسة نشطة
|
| 240 |
-
POST /api/sessions/cleanup # تنظيف الجلسات المكتملة
|
| 241 |
-
|
| 242 |
-
# إدارة البيانات الطبية
|
| 243 |
-
GET /api/medical-datasets # قائمة قواعد البيانات المتاحة
|
| 244 |
-
POST /api/medical-datasets/select # حفظ اختيارات المستخدم
|
| 245 |
-
GET /api/medical-datasets/selections/{session} # استرجاع الاختيارات
|
| 246 |
-
GET /api/medical-datasets/recommendations/{session} # توصيات مخصصة
|
| 247 |
-
|
| 248 |
-
# إدارة النماذج
|
| 249 |
-
GET /api/google-models # قائمة نماذج Google المتاحة
|
| 250 |
-
POST /api/model-configuration/save # حفظ تكوين النماذج
|
| 251 |
-
GET /api/model-configuration/{session} # استرجاع التكوين المحفوظ
|
| 252 |
-
```
|
| 253 |
-
|
| 254 |
-
#### قاعدة البيانات المطورة
|
| 255 |
-
```sql
|
| 256 |
-
-- جدول اختيارات قواعد البيانات الطبية
|
| 257 |
-
medical_dataset_selections (
|
| 258 |
-
id, user_session, dataset_name, dataset_config,
|
| 259 |
-
selected_at, is_active, selection_metadata
|
| 260 |
-
)
|
| 261 |
-
|
| 262 |
-
-- جدول تفضيلات المستخدم الطبية
|
| 263 |
-
user_medical_preferences (
|
| 264 |
-
id, user_session, preferred_specialties, experience_level,
|
| 265 |
-
preferred_languages, training_preferences, created_at, updated_at
|
| 266 |
-
)
|
| 267 |
-
|
| 268 |
-
-- جدول جلسات التدريب الطبي
|
| 269 |
-
medical_training_sessions (
|
| 270 |
-
id, session_id, user_session, selected_datasets,
|
| 271 |
-
training_config, medical_metrics, status, created_at, completed_at
|
| 272 |
-
)
|
| 273 |
-
```
|
| 274 |
-
|
| 275 |
-
### 🎯 الاستخدام المحسن
|
| 276 |
-
|
| 277 |
-
#### 1. إدارة قواعد البيانات الطبية
|
| 278 |
-
```
|
| 279 |
-
1. انتقل إلى صفحة "البيانات الطبية"
|
| 280 |
-
2. اختر التخصص الطبي المطلوب
|
| 281 |
-
3. حدد قواعد البيانات المناسبة
|
| 282 |
-
4. احفظ الاختيارات (حفظ تلقائي كل 30 ثانية)
|
| 283 |
-
5. راجع التوصيات المخصصة
|
| 284 |
-
```
|
| 285 |
-
|
| 286 |
-
#### 2. إدارة النماذج المعلمة والطلابية
|
| 287 |
-
```
|
| 288 |
-
1. انتقل إلى صفحة "نماذج Google"
|
| 289 |
-
2. اختر النماذج المعلمة (يمكن اختيار متعددة)
|
| 290 |
-
3. حدد النموذج الطلابي (جديد أو موجود)
|
| 291 |
-
4. احفظ التكوين
|
| 292 |
-
5. عد للصفحة الرئيسية لبدء التدريب
|
| 293 |
-
```
|
| 294 |
-
|
| 295 |
-
#### 3. التدريب المحسن
|
| 296 |
-
```
|
| 297 |
-
1. النماذج وقواعد البيانات محفوظة تلقائياً
|
| 298 |
-
2. بدء التدريب مع Loss حقيقي ومتغير
|
| 299 |
-
3. مراقبة التقدم في الوقت الفعلي
|
| 300 |
-
4. إدارة الجلسات (إيقاف، استئناف، حذف)
|
| 301 |
-
5. تحميل النموذج المدرب
|
| 302 |
-
```
|
| 303 |
-
|
| 304 |
-
### 📊 مقاييس الجودة الطبية
|
| 305 |
-
|
| 306 |
-
#### مؤشرات الأداء المطورة
|
| 307 |
-
- **دقة التشخيص**: > 95% (الهدف)
|
| 308 |
-
- **الحساسية**: > 90% (اكتشاف الحالات الإيجابية)
|
| 309 |
-
- **النوعية**: > 95% (تجنب الإيجابيات الكاذبة)
|
| 310 |
-
- **نتيجة F1**: > 92% (التوازن بين الدقة والاستدعاء)
|
| 311 |
-
|
| 312 |
-
#### التخصصات الطبية المدعومة
|
| 313 |
-
- **الأشعة الطبية**: تحليل الصور الشعاعية والمقطعية
|
| 314 |
-
- **أمراض القلب**: تشخيص أمراض القلب والأوعية الدموية
|
| 315 |
-
- **الأمراض العصبية**: تحليل اضطرابات الجهاز العصبي
|
| 316 |
-
- **علم الأورام**: اكتشاف وتحليل الأورام السرطانية
|
| 317 |
-
- **الطب الطارئ**: التشخيص السريع في الحالات الحرجة
|
| 318 |
-
|
| 319 |
-
### 🚀 النشر والتشغيل
|
| 320 |
-
|
| 321 |
-
#### متطلبات Hugging Face Spaces
|
| 322 |
-
- ✅ **الذاكرة**: محسن للعمل ضمن حدود 16GB
|
| 323 |
-
- ✅ **المعالجة**: تدريب متدرج مع إيقاف تلقائي
|
| 324 |
-
- ✅ **التخزين**: قاعدة بيانات SQLite محلية
|
| 325 |
-
- ✅ **الشبكة**: WebSocket مستقر مع معالجة أخطاء شاملة
|
| 326 |
-
|
| 327 |
-
#### خطوات النشر السريع
|
| 328 |
-
```bash
|
| 329 |
-
# 1. التحقق من التطبيق محلياً
|
| 330 |
-
python app.py
|
| 331 |
-
|
| 332 |
-
# 2. اختبار APIs الجديدة
|
| 333 |
-
curl http://localhost:7860/api/medical-datasets
|
| 334 |
-
curl http://localhost:7860/api/google-models
|
| 335 |
-
|
| 336 |
-
# 3. النشر على HF Spaces
|
| 337 |
-
git add .
|
| 338 |
-
git commit -m "النسخة الوظيفية الكاملة 2.0"
|
| 339 |
-
git push origin main
|
| 340 |
-
```
|
| 341 |
-
|
| 342 |
-
### 📈 النتائج المحققة
|
| 343 |
-
|
| 344 |
-
#### قبل التطوير
|
| 345 |
-
- ❌ Loss ثابت على 0.0000
|
| 346 |
-
- ❌ أخطاء في إدارة الجلسات
|
| 347 |
-
- ❌ واجهات غير وظيفية
|
| 348 |
-
- ❌ عدم تكامل المكونات
|
| 349 |
-
|
| 350 |
-
#### بعد التطوير
|
| 351 |
-
- ✅ Loss حقيقي ومتغير (0.1 - 2.5)
|
| 352 |
-
- ✅ إدارة جلسات موثوقة 100%
|
| 353 |
-
- ✅ واجهات تفاعلية كاملة
|
| 354 |
-
- ✅ تكامل سلس بين جميع المكونات
|
| 355 |
-
- ✅ قاعدة بيانات وظيفية مع APIs
|
| 356 |
-
- ✅ نظام إدارة نماذج متقدم
|
| 357 |
-
|
| 358 |
-
---
|
| 359 |
-
|
| 360 |
-
## 📞 الدعم والمساعدة
|
| 361 |
-
|
| 362 |
-
### للمطورين
|
| 363 |
-
- **الوثائق التقنية**: راجع ملفات `/docs`
|
| 364 |
-
- **أمثلة الكود**: راجع `/examples`
|
| 365 |
-
- **اختبار APIs**: استخدم `/api/docs` للتوثيق التفاعلي
|
| 366 |
-
|
| 367 |
-
### للمستخدمين الطبيين
|
| 368 |
-
- **دليل الاستخدام**: متوفر في الواجهة
|
| 369 |
-
- **التدريب**: فيديوهات تعليمية قادمة
|
| 370 |
-
- **الدعم الفني**: متوفر عبر GitHub Issues
|
| 371 |
-
|
| 372 |
-
### للباحثين
|
| 373 |
-
- **البيانات المفتوحة**: جميع قواعد البيانات مفتوحة المصدر
|
| 374 |
-
- **النماذج المدربة**: متاحة للتحميل والاستخدام
|
| 375 |
-
- **النشر العلمي**: مرحب بالاستشهاد والنشر
|
| 376 |
-
|
| 377 |
-
---
|
| 378 |
-
|
| 379 |
-
**🎉 المنصة جاهزة الآن للاستخدام الفعلي في تدريب نماذج الذكاء الاصطناعي الطبي!**
|
| 380 |
- 16GB+ RAM
|
| 381 |
- 100GB+ free disk space
|
| 382 |
- Fast SSD storage
|
|
|
|
| 202 |
- Intel CPU with MKL support
|
| 203 |
|
| 204 |
#### For Medical AI
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 205 |
- 16GB+ RAM
|
| 206 |
- 100GB+ free disk space
|
| 207 |
- Fast SSD storage
|
app.py
CHANGED
|
@@ -77,41 +77,6 @@ templates = Jinja2Templates(directory="templates")
|
|
| 77 |
training_sessions: Dict[str, Dict[str, Any]] = {}
|
| 78 |
active_connections: Dict[str, WebSocket] = {}
|
| 79 |
|
| 80 |
-
def serialize_session_for_websocket(session_data: Dict[str, Any]) -> Dict[str, Any]:
|
| 81 |
-
"""
|
| 82 |
-
Clean session data for WebSocket JSON serialization
|
| 83 |
-
Converts Path objects and other non-serializable types to strings
|
| 84 |
-
"""
|
| 85 |
-
cleaned_data = {}
|
| 86 |
-
|
| 87 |
-
for key, value in session_data.items():
|
| 88 |
-
try:
|
| 89 |
-
if isinstance(value, Path):
|
| 90 |
-
# Convert Path objects to strings
|
| 91 |
-
cleaned_data[key] = str(value)
|
| 92 |
-
elif isinstance(value, (list, tuple)):
|
| 93 |
-
# Clean lists/tuples recursively
|
| 94 |
-
cleaned_data[key] = [
|
| 95 |
-
str(item) if isinstance(item, Path) else
|
| 96 |
-
serialize_session_for_websocket(item) if isinstance(item, dict) else
|
| 97 |
-
item for item in value
|
| 98 |
-
]
|
| 99 |
-
elif isinstance(value, dict):
|
| 100 |
-
# Clean nested dictionaries recursively
|
| 101 |
-
cleaned_data[key] = serialize_session_for_websocket(value)
|
| 102 |
-
elif hasattr(value, '__dict__') and not isinstance(value, (str, int, float, bool, type(None))):
|
| 103 |
-
# Convert complex objects to string representation
|
| 104 |
-
cleaned_data[key] = str(value)
|
| 105 |
-
else:
|
| 106 |
-
# Keep simple types as-is
|
| 107 |
-
cleaned_data[key] = value
|
| 108 |
-
except Exception as e:
|
| 109 |
-
# If anything fails, convert to string
|
| 110 |
-
logger.warning(f"Error serializing session key '{key}': {e}")
|
| 111 |
-
cleaned_data[key] = str(value) if value is not None else None
|
| 112 |
-
|
| 113 |
-
return cleaned_data
|
| 114 |
-
|
| 115 |
# Pydantic models for API
|
| 116 |
class TrainingConfig(BaseModel):
|
| 117 |
session_id: str = Field(..., description="Unique session identifier")
|
|
@@ -385,28 +350,9 @@ async def start_training(
|
|
| 385 |
try:
|
| 386 |
session_id = config.session_id
|
| 387 |
|
| 388 |
-
#
|
| 389 |
if session_id in training_sessions:
|
| 390 |
-
|
| 391 |
-
status = existing_session.get("status", "unknown")
|
| 392 |
-
|
| 393 |
-
# If session is completed or failed, allow reuse by cleaning it up
|
| 394 |
-
if status in ["completed", "failed"]:
|
| 395 |
-
logger.info(f"Cleaning up previous session {session_id} with status: {status}")
|
| 396 |
-
del training_sessions[session_id]
|
| 397 |
-
# Also clean up WebSocket connection if exists
|
| 398 |
-
if session_id in active_connections:
|
| 399 |
-
try:
|
| 400 |
-
await active_connections[session_id].close()
|
| 401 |
-
except:
|
| 402 |
-
pass
|
| 403 |
-
del active_connections[session_id]
|
| 404 |
-
else:
|
| 405 |
-
# Session is still active
|
| 406 |
-
raise HTTPException(
|
| 407 |
-
status_code=400,
|
| 408 |
-
detail=f"Training session already exists with status: {status}. Please wait for completion or use a different session ID."
|
| 409 |
-
)
|
| 410 |
|
| 411 |
# Set HF token from environment if available
|
| 412 |
hf_token = os.getenv('HF_TOKEN') or os.getenv('HUGGINGFACE_TOKEN')
|
|
@@ -737,20 +683,16 @@ async def update_training_status(
|
|
| 737 |
eta = f"{int(eta_seconds // 60)}m {int(eta_seconds % 60)}s"
|
| 738 |
session["eta"] = eta
|
| 739 |
|
| 740 |
-
# Notify WebSocket clients
|
| 741 |
if session_id in active_connections:
|
| 742 |
try:
|
| 743 |
-
# Clean session data for JSON serialization
|
| 744 |
-
clean_session_data = serialize_session_for_websocket(session)
|
| 745 |
await active_connections[session_id].send_json({
|
| 746 |
"type": "training_update",
|
| 747 |
-
"data":
|
| 748 |
})
|
| 749 |
-
except
|
| 750 |
-
logger.warning(f"WebSocket error for session {session_id}: {ws_error}")
|
| 751 |
# Remove disconnected client
|
| 752 |
-
|
| 753 |
-
del active_connections[session_id]
|
| 754 |
|
| 755 |
@app.get("/progress/{session_id}", response_model=TrainingStatus)
|
| 756 |
async def get_training_progress(session_id: str):
|
|
@@ -1196,10 +1138,9 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str):
|
|
| 1196 |
try:
|
| 1197 |
# Send current status if session exists
|
| 1198 |
if session_id in training_sessions:
|
| 1199 |
-
clean_session_data = serialize_session_for_websocket(training_sessions[session_id])
|
| 1200 |
await websocket.send_json({
|
| 1201 |
"type": "training_update",
|
| 1202 |
-
"data":
|
| 1203 |
})
|
| 1204 |
|
| 1205 |
# Keep connection alive
|
|
@@ -1216,493 +1157,6 @@ async def websocket_endpoint(websocket: WebSocket, session_id: str):
|
|
| 1216 |
|
| 1217 |
# ==================== NEW ADVANCED ENDPOINTS ====================
|
| 1218 |
|
| 1219 |
-
# Session Management Endpoints
|
| 1220 |
-
@app.get("/api/sessions")
|
| 1221 |
-
async def list_training_sessions():
|
| 1222 |
-
"""List all training sessions with their status"""
|
| 1223 |
-
try:
|
| 1224 |
-
sessions_info = []
|
| 1225 |
-
for session_id, session_data in training_sessions.items():
|
| 1226 |
-
session_info = {
|
| 1227 |
-
"session_id": session_id,
|
| 1228 |
-
"status": session_data.get("status", "unknown"),
|
| 1229 |
-
"progress": session_data.get("progress", 0.0),
|
| 1230 |
-
"current_step": session_data.get("current_step", 0),
|
| 1231 |
-
"total_steps": session_data.get("total_steps", 0),
|
| 1232 |
-
"start_time": session_data.get("start_time"),
|
| 1233 |
-
"end_time": session_data.get("end_time"),
|
| 1234 |
-
"message": session_data.get("message", ""),
|
| 1235 |
-
"loss": session_data.get("loss"),
|
| 1236 |
-
"model_path": str(session_data.get("model_path", "")) if session_data.get("model_path") else None
|
| 1237 |
-
}
|
| 1238 |
-
sessions_info.append(session_info)
|
| 1239 |
-
|
| 1240 |
-
return {
|
| 1241 |
-
"success": True,
|
| 1242 |
-
"sessions": sessions_info,
|
| 1243 |
-
"total_sessions": len(sessions_info),
|
| 1244 |
-
"active_sessions": len([s for s in sessions_info if s["status"] in ["running", "initializing"]])
|
| 1245 |
-
}
|
| 1246 |
-
except Exception as e:
|
| 1247 |
-
logger.error(f"Error listing sessions: {e}")
|
| 1248 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1249 |
-
|
| 1250 |
-
@app.delete("/api/sessions/{session_id}")
|
| 1251 |
-
async def delete_training_session(session_id: str):
|
| 1252 |
-
"""Delete a training session"""
|
| 1253 |
-
try:
|
| 1254 |
-
if session_id not in training_sessions:
|
| 1255 |
-
raise HTTPException(status_code=404, detail="Training session not found")
|
| 1256 |
-
|
| 1257 |
-
session = training_sessions[session_id]
|
| 1258 |
-
status = session.get("status", "unknown")
|
| 1259 |
-
|
| 1260 |
-
# Don't allow deletion of running sessions
|
| 1261 |
-
if status in ["running", "initializing"]:
|
| 1262 |
-
raise HTTPException(
|
| 1263 |
-
status_code=400,
|
| 1264 |
-
detail=f"Cannot delete active session with status: {status}"
|
| 1265 |
-
)
|
| 1266 |
-
|
| 1267 |
-
# Clean up session data
|
| 1268 |
-
del training_sessions[session_id]
|
| 1269 |
-
|
| 1270 |
-
# Clean up WebSocket connection if exists
|
| 1271 |
-
if session_id in active_connections:
|
| 1272 |
-
try:
|
| 1273 |
-
await active_connections[session_id].close()
|
| 1274 |
-
except:
|
| 1275 |
-
pass
|
| 1276 |
-
del active_connections[session_id]
|
| 1277 |
-
|
| 1278 |
-
logger.info(f"Deleted training session: {session_id}")
|
| 1279 |
-
return {
|
| 1280 |
-
"success": True,
|
| 1281 |
-
"message": f"Session {session_id} deleted successfully"
|
| 1282 |
-
}
|
| 1283 |
-
|
| 1284 |
-
except HTTPException:
|
| 1285 |
-
raise
|
| 1286 |
-
except Exception as e:
|
| 1287 |
-
logger.error(f"Error deleting session {session_id}: {e}")
|
| 1288 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1289 |
-
|
| 1290 |
-
@app.post("/api/sessions/{session_id}/cancel")
|
| 1291 |
-
async def cancel_training_session(session_id: str):
|
| 1292 |
-
"""Cancel a running training session"""
|
| 1293 |
-
try:
|
| 1294 |
-
if session_id not in training_sessions:
|
| 1295 |
-
raise HTTPException(status_code=404, detail="Training session not found")
|
| 1296 |
-
|
| 1297 |
-
session = training_sessions[session_id]
|
| 1298 |
-
status = session.get("status", "unknown")
|
| 1299 |
-
|
| 1300 |
-
if status not in ["running", "initializing"]:
|
| 1301 |
-
raise HTTPException(
|
| 1302 |
-
status_code=400,
|
| 1303 |
-
detail=f"Cannot cancel session with status: {status}"
|
| 1304 |
-
)
|
| 1305 |
-
|
| 1306 |
-
# Update session status
|
| 1307 |
-
session["status"] = "cancelled"
|
| 1308 |
-
session["message"] = "Training cancelled by user"
|
| 1309 |
-
session["end_time"] = asyncio.get_event_loop().time()
|
| 1310 |
-
|
| 1311 |
-
# Notify WebSocket clients
|
| 1312 |
-
await update_training_status(
|
| 1313 |
-
session_id, "cancelled", session.get("progress", 0),
|
| 1314 |
-
"Training cancelled by user"
|
| 1315 |
-
)
|
| 1316 |
-
|
| 1317 |
-
logger.info(f"Cancelled training session: {session_id}")
|
| 1318 |
-
return {
|
| 1319 |
-
"success": True,
|
| 1320 |
-
"message": f"Session {session_id} cancelled successfully"
|
| 1321 |
-
}
|
| 1322 |
-
|
| 1323 |
-
except HTTPException:
|
| 1324 |
-
raise
|
| 1325 |
-
except Exception as e:
|
| 1326 |
-
logger.error(f"Error cancelling session {session_id}: {e}")
|
| 1327 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1328 |
-
|
| 1329 |
-
@app.post("/api/sessions/cleanup")
|
| 1330 |
-
async def cleanup_completed_sessions():
|
| 1331 |
-
"""Clean up all completed and failed sessions"""
|
| 1332 |
-
try:
|
| 1333 |
-
cleaned_sessions = []
|
| 1334 |
-
sessions_to_remove = []
|
| 1335 |
-
|
| 1336 |
-
for session_id, session_data in training_sessions.items():
|
| 1337 |
-
status = session_data.get("status", "unknown")
|
| 1338 |
-
if status in ["completed", "failed", "cancelled"]:
|
| 1339 |
-
sessions_to_remove.append(session_id)
|
| 1340 |
-
cleaned_sessions.append({
|
| 1341 |
-
"session_id": session_id,
|
| 1342 |
-
"status": status
|
| 1343 |
-
})
|
| 1344 |
-
|
| 1345 |
-
# Remove sessions
|
| 1346 |
-
for session_id in sessions_to_remove:
|
| 1347 |
-
del training_sessions[session_id]
|
| 1348 |
-
|
| 1349 |
-
# Clean up WebSocket connections
|
| 1350 |
-
if session_id in active_connections:
|
| 1351 |
-
try:
|
| 1352 |
-
await active_connections[session_id].close()
|
| 1353 |
-
except:
|
| 1354 |
-
pass
|
| 1355 |
-
del active_connections[session_id]
|
| 1356 |
-
|
| 1357 |
-
logger.info(f"Cleaned up {len(cleaned_sessions)} completed sessions")
|
| 1358 |
-
return {
|
| 1359 |
-
"success": True,
|
| 1360 |
-
"message": f"Cleaned up {len(cleaned_sessions)} sessions",
|
| 1361 |
-
"cleaned_sessions": cleaned_sessions
|
| 1362 |
-
}
|
| 1363 |
-
|
| 1364 |
-
except Exception as e:
|
| 1365 |
-
logger.error(f"Error cleaning up sessions: {e}")
|
| 1366 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1367 |
-
|
| 1368 |
-
# Medical Dataset Management Endpoints
|
| 1369 |
-
@app.get("/api/medical-datasets")
|
| 1370 |
-
async def get_medical_datasets():
|
| 1371 |
-
"""Get all supported medical datasets"""
|
| 1372 |
-
try:
|
| 1373 |
-
from src.medical.medical_config import SUPPORTED_MEDICAL_DATASETS, MEDICAL_SPECIALTIES
|
| 1374 |
-
|
| 1375 |
-
return {
|
| 1376 |
-
"success": True,
|
| 1377 |
-
"datasets": SUPPORTED_MEDICAL_DATASETS,
|
| 1378 |
-
"specialties": MEDICAL_SPECIALTIES,
|
| 1379 |
-
"total_datasets": len(SUPPORTED_MEDICAL_DATASETS)
|
| 1380 |
-
}
|
| 1381 |
-
except Exception as e:
|
| 1382 |
-
logger.error(f"Error getting medical datasets: {e}")
|
| 1383 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1384 |
-
|
| 1385 |
-
@app.post("/api/medical-datasets/select")
|
| 1386 |
-
async def select_medical_datasets(
|
| 1387 |
-
user_session: str = Form(...),
|
| 1388 |
-
selected_datasets: str = Form(...), # JSON string of dataset names
|
| 1389 |
-
preferences: str = Form(default="{}") # JSON string of user preferences
|
| 1390 |
-
):
|
| 1391 |
-
"""Save user's medical dataset selections"""
|
| 1392 |
-
try:
|
| 1393 |
-
from database.medical_selections import MedicalSelectionsDB
|
| 1394 |
-
from src.medical.medical_config import validate_medical_dataset_selection
|
| 1395 |
-
import json
|
| 1396 |
-
|
| 1397 |
-
# Parse input data
|
| 1398 |
-
dataset_list = json.loads(selected_datasets)
|
| 1399 |
-
user_preferences = json.loads(preferences)
|
| 1400 |
-
|
| 1401 |
-
# Validate selections
|
| 1402 |
-
validation_result = validate_medical_dataset_selection(dataset_list)
|
| 1403 |
-
|
| 1404 |
-
if not validation_result['valid']:
|
| 1405 |
-
return {
|
| 1406 |
-
"success": False,
|
| 1407 |
-
"errors": validation_result['errors'],
|
| 1408 |
-
"warnings": validation_result['warnings']
|
| 1409 |
-
}
|
| 1410 |
-
|
| 1411 |
-
# Save selections to database
|
| 1412 |
-
db = MedicalSelectionsDB()
|
| 1413 |
-
|
| 1414 |
-
# Clear previous selections
|
| 1415 |
-
for dataset_name in dataset_list:
|
| 1416 |
-
db.remove_dataset_selection(user_session, dataset_name)
|
| 1417 |
-
|
| 1418 |
-
# Save new selections
|
| 1419 |
-
success_count = 0
|
| 1420 |
-
for dataset_name in dataset_list:
|
| 1421 |
-
if db.save_dataset_selection(user_session, dataset_name):
|
| 1422 |
-
success_count += 1
|
| 1423 |
-
|
| 1424 |
-
# Save user preferences
|
| 1425 |
-
if user_preferences:
|
| 1426 |
-
db.save_user_preferences(user_session, user_preferences)
|
| 1427 |
-
|
| 1428 |
-
return {
|
| 1429 |
-
"success": True,
|
| 1430 |
-
"message": f"تم حفظ {success_count} من قواعد البيانات بنجاح",
|
| 1431 |
-
"selected_count": success_count,
|
| 1432 |
-
"validation_result": validation_result
|
| 1433 |
-
}
|
| 1434 |
-
|
| 1435 |
-
except json.JSONDecodeError as e:
|
| 1436 |
-
raise HTTPException(status_code=400, detail=f"Invalid JSON format: {e}")
|
| 1437 |
-
except Exception as e:
|
| 1438 |
-
logger.error(f"Error selecting medical datasets: {e}")
|
| 1439 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1440 |
-
|
| 1441 |
-
@app.get("/api/medical-datasets/selections/{user_session}")
|
| 1442 |
-
async def get_user_medical_selections(user_session: str):
|
| 1443 |
-
"""Get user's medical dataset selections"""
|
| 1444 |
-
try:
|
| 1445 |
-
from database.medical_selections import MedicalSelectionsDB
|
| 1446 |
-
|
| 1447 |
-
db = MedicalSelectionsDB()
|
| 1448 |
-
selections = db.get_user_dataset_selections(user_session)
|
| 1449 |
-
preferences = db.get_user_preferences(user_session)
|
| 1450 |
-
|
| 1451 |
-
return {
|
| 1452 |
-
"success": True,
|
| 1453 |
-
"selections": selections,
|
| 1454 |
-
"preferences": preferences,
|
| 1455 |
-
"total_selected": len(selections)
|
| 1456 |
-
}
|
| 1457 |
-
|
| 1458 |
-
except Exception as e:
|
| 1459 |
-
logger.error(f"Error getting user selections: {e}")
|
| 1460 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1461 |
-
|
| 1462 |
-
@app.delete("/api/medical-datasets/selections/{user_session}/{dataset_name}")
|
| 1463 |
-
async def remove_medical_dataset_selection(user_session: str, dataset_name: str):
|
| 1464 |
-
"""Remove a specific dataset selection"""
|
| 1465 |
-
try:
|
| 1466 |
-
from database.medical_selections import MedicalSelectionsDB
|
| 1467 |
-
|
| 1468 |
-
db = MedicalSelectionsDB()
|
| 1469 |
-
success = db.remove_dataset_selection(user_session, dataset_name)
|
| 1470 |
-
|
| 1471 |
-
if success:
|
| 1472 |
-
return {
|
| 1473 |
-
"success": True,
|
| 1474 |
-
"message": f"تم إزالة قاعدة البيانات {dataset_name} بنجاح"
|
| 1475 |
-
}
|
| 1476 |
-
else:
|
| 1477 |
-
raise HTTPException(status_code=400, detail="فشل في إزالة قاعدة البيانات")
|
| 1478 |
-
|
| 1479 |
-
except Exception as e:
|
| 1480 |
-
logger.error(f"Error removing dataset selection: {e}")
|
| 1481 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1482 |
-
|
| 1483 |
-
@app.get("/api/medical-datasets/recommendations/{user_session}")
|
| 1484 |
-
async def get_dataset_recommendations(user_session: str):
|
| 1485 |
-
"""Get personalized dataset recommendations"""
|
| 1486 |
-
try:
|
| 1487 |
-
from database.medical_selections import MedicalSelectionsDB
|
| 1488 |
-
from src.medical.medical_config import get_dataset_by_specialty, SUPPORTED_MEDICAL_DATASETS
|
| 1489 |
-
|
| 1490 |
-
db = MedicalSelectionsDB()
|
| 1491 |
-
preferences = db.get_user_preferences(user_session)
|
| 1492 |
-
|
| 1493 |
-
recommendations = []
|
| 1494 |
-
|
| 1495 |
-
# Get recommendations based on specialties
|
| 1496 |
-
for specialty in preferences.get('specialties', []):
|
| 1497 |
-
recommended_datasets = get_dataset_by_specialty(specialty)
|
| 1498 |
-
for dataset_name in recommended_datasets:
|
| 1499 |
-
if dataset_name in SUPPORTED_MEDICAL_DATASETS:
|
| 1500 |
-
dataset_info = SUPPORTED_MEDICAL_DATASETS[dataset_name].copy()
|
| 1501 |
-
dataset_info['recommended_for_specialty'] = specialty
|
| 1502 |
-
dataset_info['dataset_key'] = dataset_name
|
| 1503 |
-
recommendations.append(dataset_info)
|
| 1504 |
-
|
| 1505 |
-
# Remove duplicates
|
| 1506 |
-
seen_datasets = set()
|
| 1507 |
-
unique_recommendations = []
|
| 1508 |
-
for rec in recommendations:
|
| 1509 |
-
if rec['dataset_key'] not in seen_datasets:
|
| 1510 |
-
seen_datasets.add(rec['dataset_key'])
|
| 1511 |
-
unique_recommendations.append(rec)
|
| 1512 |
-
|
| 1513 |
-
return {
|
| 1514 |
-
"success": True,
|
| 1515 |
-
"recommendations": unique_recommendations,
|
| 1516 |
-
"user_preferences": preferences,
|
| 1517 |
-
"total_recommendations": len(unique_recommendations)
|
| 1518 |
-
}
|
| 1519 |
-
|
| 1520 |
-
except Exception as e:
|
| 1521 |
-
logger.error(f"Error getting recommendations: {e}")
|
| 1522 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1523 |
-
|
| 1524 |
-
# Model Management Endpoints
|
| 1525 |
-
@app.get("/api/google-models")
|
| 1526 |
-
async def get_google_models():
|
| 1527 |
-
"""Get available Google models for teacher selection"""
|
| 1528 |
-
try:
|
| 1529 |
-
# Mock Google models data - in production, this would fetch from Google's API
|
| 1530 |
-
google_models = {
|
| 1531 |
-
'flan_t5_base': {
|
| 1532 |
-
'name': 'FLAN-T5 Base',
|
| 1533 |
-
'description': 'نموذج نصوص متوسط الحجم مدرب على مهام متنوعة',
|
| 1534 |
-
'type': 'text',
|
| 1535 |
-
'modalities': ['text'],
|
| 1536 |
-
'parameters': '250M',
|
| 1537 |
-
'size_category': 'medium',
|
| 1538 |
-
'use_cases': ['الإجابة على الأسئلة', 'التلخيص', 'الترجمة'],
|
| 1539 |
-
'performance_score': 8.5,
|
| 1540 |
-
'repo_id': 'google/flan-t5-base',
|
| 1541 |
-
'license': 'Apache 2.0'
|
| 1542 |
-
},
|
| 1543 |
-
'flan_t5_large': {
|
| 1544 |
-
'name': 'FLAN-T5 Large',
|
| 1545 |
-
'description': 'نموذج نصوص كبير عالي الأداء',
|
| 1546 |
-
'type': 'text',
|
| 1547 |
-
'modalities': ['text'],
|
| 1548 |
-
'parameters': '780M',
|
| 1549 |
-
'size_category': 'large',
|
| 1550 |
-
'use_cases': ['المهام المعقدة', 'التحليل المتقدم', 'الكتابة الإبداعية'],
|
| 1551 |
-
'performance_score': 9.2,
|
| 1552 |
-
'repo_id': 'google/flan-t5-large',
|
| 1553 |
-
'license': 'Apache 2.0'
|
| 1554 |
-
},
|
| 1555 |
-
'vit_base': {
|
| 1556 |
-
'name': 'Vision Transformer Base',
|
| 1557 |
-
'description': 'نموذج رؤية حاسوبية متقدم',
|
| 1558 |
-
'type': 'vision',
|
| 1559 |
-
'modalities': ['vision'],
|
| 1560 |
-
'parameters': '86M',
|
| 1561 |
-
'size_category': 'medium',
|
| 1562 |
-
'use_cases': ['تصنيف الصور', 'التعرف على الأشياء', 'تحليل المحتوى البصري'],
|
| 1563 |
-
'performance_score': 8.8,
|
| 1564 |
-
'repo_id': 'google/vit-base-patch16-224',
|
| 1565 |
-
'license': 'Apache 2.0'
|
| 1566 |
-
},
|
| 1567 |
-
'clip_vit': {
|
| 1568 |
-
'name': 'CLIP Vision-Text',
|
| 1569 |
-
'description': 'نموذج متعدد الوسائط يربط النصوص والصور',
|
| 1570 |
-
'type': 'multimodal',
|
| 1571 |
-
'modalities': ['text', 'vision'],
|
| 1572 |
-
'parameters': '400M',
|
| 1573 |
-
'size_category': 'large',
|
| 1574 |
-
'use_cases': ['البحث بالصور', 'وصف الصور', 'التصنيف متعدد الوسائط'],
|
| 1575 |
-
'performance_score': 9.0,
|
| 1576 |
-
'repo_id': 'openai/clip-vit-base-patch32',
|
| 1577 |
-
'license': 'MIT'
|
| 1578 |
-
},
|
| 1579 |
-
'bert_base': {
|
| 1580 |
-
'name': 'BERT Base',
|
| 1581 |
-
'description': 'نموذج فهم اللغة الطبيعية الكلاسيكي',
|
| 1582 |
-
'type': 'text',
|
| 1583 |
-
'modalities': ['text'],
|
| 1584 |
-
'parameters': '110M',
|
| 1585 |
-
'size_category': 'small',
|
| 1586 |
-
'use_cases': ['تحليل المشاعر', 'تصنيف النصوص', 'استخراج المعلومات'],
|
| 1587 |
-
'performance_score': 8.0,
|
| 1588 |
-
'repo_id': 'bert-base-uncased',
|
| 1589 |
-
'license': 'Apache 2.0'
|
| 1590 |
-
}
|
| 1591 |
-
}
|
| 1592 |
-
|
| 1593 |
-
return {
|
| 1594 |
-
"success": True,
|
| 1595 |
-
"models": google_models,
|
| 1596 |
-
"total_models": len(google_models),
|
| 1597 |
-
"categories": {
|
| 1598 |
-
"text": len([m for m in google_models.values() if m['type'] == 'text']),
|
| 1599 |
-
"vision": len([m for m in google_models.values() if m['type'] == 'vision']),
|
| 1600 |
-
"multimodal": len([m for m in google_models.values() if m['type'] == 'multimodal'])
|
| 1601 |
-
}
|
| 1602 |
-
}
|
| 1603 |
-
except Exception as e:
|
| 1604 |
-
logger.error(f"Error getting Google models: {e}")
|
| 1605 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1606 |
-
|
| 1607 |
-
@app.post("/api/model-configuration/save")
|
| 1608 |
-
async def save_model_configuration(configuration: dict):
|
| 1609 |
-
"""Save user's model configuration"""
|
| 1610 |
-
try:
|
| 1611 |
-
from database.medical_selections import MedicalSelectionsDB
|
| 1612 |
-
import json
|
| 1613 |
-
|
| 1614 |
-
user_session = configuration.get('user_session')
|
| 1615 |
-
teachers = configuration.get('teachers', [])
|
| 1616 |
-
student = configuration.get('student')
|
| 1617 |
-
|
| 1618 |
-
if not user_session:
|
| 1619 |
-
raise HTTPException(status_code=400, detail="User session is required")
|
| 1620 |
-
|
| 1621 |
-
if not teachers:
|
| 1622 |
-
raise HTTPException(status_code=400, detail="At least one teacher model is required")
|
| 1623 |
-
|
| 1624 |
-
# Save to database
|
| 1625 |
-
db = MedicalSelectionsDB()
|
| 1626 |
-
|
| 1627 |
-
# Create configuration record
|
| 1628 |
-
config_data = {
|
| 1629 |
-
'teachers': teachers,
|
| 1630 |
-
'student': student,
|
| 1631 |
-
'timestamp': configuration.get('timestamp'),
|
| 1632 |
-
'total_teachers': len(teachers),
|
| 1633 |
-
'student_type': student.get('type') if student else 'new'
|
| 1634 |
-
}
|
| 1635 |
-
|
| 1636 |
-
# Save as user preferences
|
| 1637 |
-
success = db.save_user_preferences(user_session, {
|
| 1638 |
-
'model_configuration': config_data,
|
| 1639 |
-
'last_updated': configuration.get('timestamp')
|
| 1640 |
-
})
|
| 1641 |
-
|
| 1642 |
-
if success:
|
| 1643 |
-
return {
|
| 1644 |
-
"success": True,
|
| 1645 |
-
"message": f"تم حفظ تكوين {len(teachers)} نماذج معلمة بنجاح",
|
| 1646 |
-
"configuration": config_data
|
| 1647 |
-
}
|
| 1648 |
-
else:
|
| 1649 |
-
raise HTTPException(status_code=500, detail="Failed to save configuration")
|
| 1650 |
-
|
| 1651 |
-
except HTTPException:
|
| 1652 |
-
raise
|
| 1653 |
-
except Exception as e:
|
| 1654 |
-
logger.error(f"Error saving model configuration: {e}")
|
| 1655 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1656 |
-
|
| 1657 |
-
@app.get("/api/model-configuration/{user_session}")
|
| 1658 |
-
async def get_model_configuration(user_session: str):
|
| 1659 |
-
"""Get user's saved model configuration"""
|
| 1660 |
-
try:
|
| 1661 |
-
from database.medical_selections import MedicalSelectionsDB
|
| 1662 |
-
|
| 1663 |
-
db = MedicalSelectionsDB()
|
| 1664 |
-
preferences = db.get_user_preferences(user_session)
|
| 1665 |
-
|
| 1666 |
-
model_config = preferences.get('model_configuration', {})
|
| 1667 |
-
|
| 1668 |
-
return {
|
| 1669 |
-
"success": True,
|
| 1670 |
-
"teachers": model_config.get('teachers', []),
|
| 1671 |
-
"student": model_config.get('student'),
|
| 1672 |
-
"last_updated": preferences.get('last_updated'),
|
| 1673 |
-
"total_teachers": len(model_config.get('teachers', []))
|
| 1674 |
-
}
|
| 1675 |
-
|
| 1676 |
-
except Exception as e:
|
| 1677 |
-
logger.error(f"Error getting model configuration: {e}")
|
| 1678 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1679 |
-
|
| 1680 |
-
@app.delete("/api/model-configuration/{user_session}")
|
| 1681 |
-
async def clear_model_configuration(user_session: str):
|
| 1682 |
-
"""Clear user's model configuration"""
|
| 1683 |
-
try:
|
| 1684 |
-
from database.medical_selections import MedicalSelectionsDB
|
| 1685 |
-
|
| 1686 |
-
db = MedicalSelectionsDB()
|
| 1687 |
-
|
| 1688 |
-
# Clear model configuration
|
| 1689 |
-
success = db.save_user_preferences(user_session, {
|
| 1690 |
-
'model_configuration': {},
|
| 1691 |
-
'last_updated': None
|
| 1692 |
-
})
|
| 1693 |
-
|
| 1694 |
-
if success:
|
| 1695 |
-
return {
|
| 1696 |
-
"success": True,
|
| 1697 |
-
"message": "تم مسح تكوين النماذج بنجاح"
|
| 1698 |
-
}
|
| 1699 |
-
else:
|
| 1700 |
-
raise HTTPException(status_code=500, detail="Failed to clear configuration")
|
| 1701 |
-
|
| 1702 |
-
except Exception as e:
|
| 1703 |
-
logger.error(f"Error clearing model configuration: {e}")
|
| 1704 |
-
raise HTTPException(status_code=500, detail=str(e))
|
| 1705 |
-
|
| 1706 |
# Token Management Endpoints
|
| 1707 |
@app.get("/tokens")
|
| 1708 |
async def token_management_page(request: Request):
|
|
@@ -1835,12 +1289,6 @@ async def medical_datasets_page(request: Request):
|
|
| 1835 |
"""Medical datasets management page"""
|
| 1836 |
return templates.TemplateResponse("medical-datasets.html", {"request": request})
|
| 1837 |
|
| 1838 |
-
# Google Models Endpoints
|
| 1839 |
-
@app.get("/google-models")
|
| 1840 |
-
async def google_models_page(request: Request):
|
| 1841 |
-
"""Google models selection page"""
|
| 1842 |
-
return templates.TemplateResponse("google-models.html", {"request": request})
|
| 1843 |
-
|
| 1844 |
@app.get("/api/medical-datasets")
|
| 1845 |
async def list_medical_datasets():
|
| 1846 |
"""List supported medical datasets"""
|
|
|
|
| 77 |
training_sessions: Dict[str, Dict[str, Any]] = {}
|
| 78 |
active_connections: Dict[str, WebSocket] = {}
|
| 79 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 80 |
# Pydantic models for API
|
| 81 |
class TrainingConfig(BaseModel):
|
| 82 |
session_id: str = Field(..., description="Unique session identifier")
|
|
|
|
| 350 |
try:
|
| 351 |
session_id = config.session_id
|
| 352 |
|
| 353 |
+
# Validate session doesn't already exist
|
| 354 |
if session_id in training_sessions:
|
| 355 |
+
raise HTTPException(status_code=400, detail="Training session already exists")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 356 |
|
| 357 |
# Set HF token from environment if available
|
| 358 |
hf_token = os.getenv('HF_TOKEN') or os.getenv('HUGGINGFACE_TOKEN')
|
|
|
|
| 683 |
eta = f"{int(eta_seconds // 60)}m {int(eta_seconds % 60)}s"
|
| 684 |
session["eta"] = eta
|
| 685 |
|
| 686 |
+
# Notify WebSocket clients
|
| 687 |
if session_id in active_connections:
|
| 688 |
try:
|
|
|
|
|
|
|
| 689 |
await active_connections[session_id].send_json({
|
| 690 |
"type": "training_update",
|
| 691 |
+
"data": session
|
| 692 |
})
|
| 693 |
+
except:
|
|
|
|
| 694 |
# Remove disconnected client
|
| 695 |
+
del active_connections[session_id]
|
|
|
|
| 696 |
|
| 697 |
@app.get("/progress/{session_id}", response_model=TrainingStatus)
|
| 698 |
async def get_training_progress(session_id: str):
|
|
|
|
| 1138 |
try:
|
| 1139 |
# Send current status if session exists
|
| 1140 |
if session_id in training_sessions:
|
|
|
|
| 1141 |
await websocket.send_json({
|
| 1142 |
"type": "training_update",
|
| 1143 |
+
"data": training_sessions[session_id]
|
| 1144 |
})
|
| 1145 |
|
| 1146 |
# Keep connection alive
|
|
|
|
| 1157 |
|
| 1158 |
# ==================== NEW ADVANCED ENDPOINTS ====================
|
| 1159 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1160 |
# Token Management Endpoints
|
| 1161 |
@app.get("/tokens")
|
| 1162 |
async def token_management_page(request: Request):
|
|
|
|
| 1289 |
"""Medical datasets management page"""
|
| 1290 |
return templates.TemplateResponse("medical-datasets.html", {"request": request})
|
| 1291 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1292 |
@app.get("/api/medical-datasets")
|
| 1293 |
async def list_medical_datasets():
|
| 1294 |
"""List supported medical datasets"""
|
database/medical_selections.py
DELETED
|
@@ -1,367 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Database models and operations for medical dataset selections
|
| 3 |
-
"""
|
| 4 |
-
|
| 5 |
-
import sqlite3
|
| 6 |
-
import json
|
| 7 |
-
import logging
|
| 8 |
-
from typing import List, Dict, Any, Optional
|
| 9 |
-
from datetime import datetime
|
| 10 |
-
from pathlib import Path
|
| 11 |
-
|
| 12 |
-
logger = logging.getLogger(__name__)
|
| 13 |
-
|
| 14 |
-
class MedicalSelectionsDB:
|
| 15 |
-
"""Database manager for medical dataset selections"""
|
| 16 |
-
|
| 17 |
-
def __init__(self, db_path: str = "database/medical_selections.db"):
|
| 18 |
-
self.db_path = Path(db_path)
|
| 19 |
-
self.db_path.parent.mkdir(parents=True, exist_ok=True)
|
| 20 |
-
self.init_database()
|
| 21 |
-
|
| 22 |
-
def init_database(self):
|
| 23 |
-
"""Initialize database tables"""
|
| 24 |
-
try:
|
| 25 |
-
with sqlite3.connect(self.db_path) as conn:
|
| 26 |
-
cursor = conn.cursor()
|
| 27 |
-
|
| 28 |
-
# Medical dataset selections table
|
| 29 |
-
cursor.execute('''
|
| 30 |
-
CREATE TABLE IF NOT EXISTS medical_dataset_selections (
|
| 31 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 32 |
-
user_session TEXT NOT NULL,
|
| 33 |
-
dataset_name TEXT NOT NULL,
|
| 34 |
-
dataset_config TEXT,
|
| 35 |
-
selected_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 36 |
-
is_active BOOLEAN DEFAULT TRUE,
|
| 37 |
-
selection_metadata TEXT
|
| 38 |
-
)
|
| 39 |
-
''')
|
| 40 |
-
|
| 41 |
-
# User preferences table
|
| 42 |
-
cursor.execute('''
|
| 43 |
-
CREATE TABLE IF NOT EXISTS user_medical_preferences (
|
| 44 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 45 |
-
user_session TEXT NOT NULL UNIQUE,
|
| 46 |
-
preferred_specialties TEXT,
|
| 47 |
-
experience_level TEXT DEFAULT 'intermediate',
|
| 48 |
-
preferred_languages TEXT,
|
| 49 |
-
training_preferences TEXT,
|
| 50 |
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 51 |
-
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
| 52 |
-
)
|
| 53 |
-
''')
|
| 54 |
-
|
| 55 |
-
# Training sessions with medical data
|
| 56 |
-
cursor.execute('''
|
| 57 |
-
CREATE TABLE IF NOT EXISTS medical_training_sessions (
|
| 58 |
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
| 59 |
-
session_id TEXT NOT NULL UNIQUE,
|
| 60 |
-
user_session TEXT NOT NULL,
|
| 61 |
-
selected_datasets TEXT NOT NULL,
|
| 62 |
-
training_config TEXT,
|
| 63 |
-
medical_metrics TEXT,
|
| 64 |
-
status TEXT DEFAULT 'created',
|
| 65 |
-
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
| 66 |
-
completed_at TIMESTAMP,
|
| 67 |
-
results_summary TEXT
|
| 68 |
-
)
|
| 69 |
-
''')
|
| 70 |
-
|
| 71 |
-
conn.commit()
|
| 72 |
-
logger.info("Medical selections database initialized successfully")
|
| 73 |
-
|
| 74 |
-
except Exception as e:
|
| 75 |
-
logger.error(f"Error initializing medical selections database: {e}")
|
| 76 |
-
raise
|
| 77 |
-
|
| 78 |
-
def save_dataset_selection(self, user_session: str, dataset_name: str,
|
| 79 |
-
dataset_config: Dict[str, Any] = None,
|
| 80 |
-
metadata: Dict[str, Any] = None) -> bool:
|
| 81 |
-
"""Save a medical dataset selection"""
|
| 82 |
-
try:
|
| 83 |
-
with sqlite3.connect(self.db_path) as conn:
|
| 84 |
-
cursor = conn.cursor()
|
| 85 |
-
|
| 86 |
-
# Deactivate previous selections for this dataset
|
| 87 |
-
cursor.execute('''
|
| 88 |
-
UPDATE medical_dataset_selections
|
| 89 |
-
SET is_active = FALSE
|
| 90 |
-
WHERE user_session = ? AND dataset_name = ?
|
| 91 |
-
''', (user_session, dataset_name))
|
| 92 |
-
|
| 93 |
-
# Insert new selection
|
| 94 |
-
cursor.execute('''
|
| 95 |
-
INSERT INTO medical_dataset_selections
|
| 96 |
-
(user_session, dataset_name, dataset_config, selection_metadata)
|
| 97 |
-
VALUES (?, ?, ?, ?)
|
| 98 |
-
''', (
|
| 99 |
-
user_session,
|
| 100 |
-
dataset_name,
|
| 101 |
-
json.dumps(dataset_config) if dataset_config else None,
|
| 102 |
-
json.dumps(metadata) if metadata else None
|
| 103 |
-
))
|
| 104 |
-
|
| 105 |
-
conn.commit()
|
| 106 |
-
logger.info(f"Saved dataset selection: {dataset_name} for session {user_session}")
|
| 107 |
-
return True
|
| 108 |
-
|
| 109 |
-
except Exception as e:
|
| 110 |
-
logger.error(f"Error saving dataset selection: {e}")
|
| 111 |
-
return False
|
| 112 |
-
|
| 113 |
-
def get_user_dataset_selections(self, user_session: str) -> List[Dict[str, Any]]:
|
| 114 |
-
"""Get active dataset selections for a user session"""
|
| 115 |
-
try:
|
| 116 |
-
with sqlite3.connect(self.db_path) as conn:
|
| 117 |
-
cursor = conn.cursor()
|
| 118 |
-
|
| 119 |
-
cursor.execute('''
|
| 120 |
-
SELECT dataset_name, dataset_config, selected_at, selection_metadata
|
| 121 |
-
FROM medical_dataset_selections
|
| 122 |
-
WHERE user_session = ? AND is_active = TRUE
|
| 123 |
-
ORDER BY selected_at DESC
|
| 124 |
-
''', (user_session,))
|
| 125 |
-
|
| 126 |
-
results = []
|
| 127 |
-
for row in cursor.fetchall():
|
| 128 |
-
dataset_name, config_json, selected_at, metadata_json = row
|
| 129 |
-
|
| 130 |
-
result = {
|
| 131 |
-
'dataset_name': dataset_name,
|
| 132 |
-
'selected_at': selected_at,
|
| 133 |
-
'dataset_config': json.loads(config_json) if config_json else {},
|
| 134 |
-
'metadata': json.loads(metadata_json) if metadata_json else {}
|
| 135 |
-
}
|
| 136 |
-
results.append(result)
|
| 137 |
-
|
| 138 |
-
return results
|
| 139 |
-
|
| 140 |
-
except Exception as e:
|
| 141 |
-
logger.error(f"Error getting dataset selections: {e}")
|
| 142 |
-
return []
|
| 143 |
-
|
| 144 |
-
def remove_dataset_selection(self, user_session: str, dataset_name: str) -> bool:
|
| 145 |
-
"""Remove a dataset selection"""
|
| 146 |
-
try:
|
| 147 |
-
with sqlite3.connect(self.db_path) as conn:
|
| 148 |
-
cursor = conn.cursor()
|
| 149 |
-
|
| 150 |
-
cursor.execute('''
|
| 151 |
-
UPDATE medical_dataset_selections
|
| 152 |
-
SET is_active = FALSE
|
| 153 |
-
WHERE user_session = ? AND dataset_name = ?
|
| 154 |
-
''', (user_session, dataset_name))
|
| 155 |
-
|
| 156 |
-
conn.commit()
|
| 157 |
-
logger.info(f"Removed dataset selection: {dataset_name} for session {user_session}")
|
| 158 |
-
return True
|
| 159 |
-
|
| 160 |
-
except Exception as e:
|
| 161 |
-
logger.error(f"Error removing dataset selection: {e}")
|
| 162 |
-
return False
|
| 163 |
-
|
| 164 |
-
def save_user_preferences(self, user_session: str, preferences: Dict[str, Any]) -> bool:
|
| 165 |
-
"""Save user medical preferences"""
|
| 166 |
-
try:
|
| 167 |
-
with sqlite3.connect(self.db_path) as conn:
|
| 168 |
-
cursor = conn.cursor()
|
| 169 |
-
|
| 170 |
-
# Check if preferences exist
|
| 171 |
-
cursor.execute('''
|
| 172 |
-
SELECT id FROM user_medical_preferences WHERE user_session = ?
|
| 173 |
-
''', (user_session,))
|
| 174 |
-
|
| 175 |
-
if cursor.fetchone():
|
| 176 |
-
# Update existing preferences
|
| 177 |
-
cursor.execute('''
|
| 178 |
-
UPDATE user_medical_preferences
|
| 179 |
-
SET preferred_specialties = ?,
|
| 180 |
-
experience_level = ?,
|
| 181 |
-
preferred_languages = ?,
|
| 182 |
-
training_preferences = ?,
|
| 183 |
-
updated_at = CURRENT_TIMESTAMP
|
| 184 |
-
WHERE user_session = ?
|
| 185 |
-
''', (
|
| 186 |
-
json.dumps(preferences.get('specialties', [])),
|
| 187 |
-
preferences.get('experience_level', 'intermediate'),
|
| 188 |
-
json.dumps(preferences.get('languages', ['ar', 'en'])),
|
| 189 |
-
json.dumps(preferences.get('training_preferences', {})),
|
| 190 |
-
user_session
|
| 191 |
-
))
|
| 192 |
-
else:
|
| 193 |
-
# Insert new preferences
|
| 194 |
-
cursor.execute('''
|
| 195 |
-
INSERT INTO user_medical_preferences
|
| 196 |
-
(user_session, preferred_specialties, experience_level,
|
| 197 |
-
preferred_languages, training_preferences)
|
| 198 |
-
VALUES (?, ?, ?, ?, ?)
|
| 199 |
-
''', (
|
| 200 |
-
user_session,
|
| 201 |
-
json.dumps(preferences.get('specialties', [])),
|
| 202 |
-
preferences.get('experience_level', 'intermediate'),
|
| 203 |
-
json.dumps(preferences.get('languages', ['ar', 'en'])),
|
| 204 |
-
json.dumps(preferences.get('training_preferences', {}))
|
| 205 |
-
))
|
| 206 |
-
|
| 207 |
-
conn.commit()
|
| 208 |
-
logger.info(f"Saved user preferences for session {user_session}")
|
| 209 |
-
return True
|
| 210 |
-
|
| 211 |
-
except Exception as e:
|
| 212 |
-
logger.error(f"Error saving user preferences: {e}")
|
| 213 |
-
return False
|
| 214 |
-
|
| 215 |
-
def get_user_preferences(self, user_session: str) -> Dict[str, Any]:
|
| 216 |
-
"""Get user medical preferences"""
|
| 217 |
-
try:
|
| 218 |
-
with sqlite3.connect(self.db_path) as conn:
|
| 219 |
-
cursor = conn.cursor()
|
| 220 |
-
|
| 221 |
-
cursor.execute('''
|
| 222 |
-
SELECT preferred_specialties, experience_level,
|
| 223 |
-
preferred_languages, training_preferences
|
| 224 |
-
FROM user_medical_preferences
|
| 225 |
-
WHERE user_session = ?
|
| 226 |
-
''', (user_session,))
|
| 227 |
-
|
| 228 |
-
row = cursor.fetchone()
|
| 229 |
-
if row:
|
| 230 |
-
specialties_json, level, languages_json, training_json = row
|
| 231 |
-
return {
|
| 232 |
-
'specialties': json.loads(specialties_json) if specialties_json else [],
|
| 233 |
-
'experience_level': level,
|
| 234 |
-
'languages': json.loads(languages_json) if languages_json else ['ar', 'en'],
|
| 235 |
-
'training_preferences': json.loads(training_json) if training_json else {}
|
| 236 |
-
}
|
| 237 |
-
else:
|
| 238 |
-
# Return default preferences
|
| 239 |
-
return {
|
| 240 |
-
'specialties': [],
|
| 241 |
-
'experience_level': 'intermediate',
|
| 242 |
-
'languages': ['ar', 'en'],
|
| 243 |
-
'training_preferences': {}
|
| 244 |
-
}
|
| 245 |
-
|
| 246 |
-
except Exception as e:
|
| 247 |
-
logger.error(f"Error getting user preferences: {e}")
|
| 248 |
-
return {
|
| 249 |
-
'specialties': [],
|
| 250 |
-
'experience_level': 'intermediate',
|
| 251 |
-
'languages': ['ar', 'en'],
|
| 252 |
-
'training_preferences': {}
|
| 253 |
-
}
|
| 254 |
-
|
| 255 |
-
def save_training_session(self, session_id: str, user_session: str,
|
| 256 |
-
selected_datasets: List[str], training_config: Dict[str, Any],
|
| 257 |
-
medical_metrics: Dict[str, Any] = None) -> bool:
|
| 258 |
-
"""Save a medical training session"""
|
| 259 |
-
try:
|
| 260 |
-
with sqlite3.connect(self.db_path) as conn:
|
| 261 |
-
cursor = conn.cursor()
|
| 262 |
-
|
| 263 |
-
cursor.execute('''
|
| 264 |
-
INSERT OR REPLACE INTO medical_training_sessions
|
| 265 |
-
(session_id, user_session, selected_datasets, training_config, medical_metrics)
|
| 266 |
-
VALUES (?, ?, ?, ?, ?)
|
| 267 |
-
''', (
|
| 268 |
-
session_id,
|
| 269 |
-
user_session,
|
| 270 |
-
json.dumps(selected_datasets),
|
| 271 |
-
json.dumps(training_config),
|
| 272 |
-
json.dumps(medical_metrics) if medical_metrics else None
|
| 273 |
-
))
|
| 274 |
-
|
| 275 |
-
conn.commit()
|
| 276 |
-
logger.info(f"Saved medical training session: {session_id}")
|
| 277 |
-
return True
|
| 278 |
-
|
| 279 |
-
except Exception as e:
|
| 280 |
-
logger.error(f"Error saving training session: {e}")
|
| 281 |
-
return False
|
| 282 |
-
|
| 283 |
-
def update_training_session_status(self, session_id: str, status: str,
|
| 284 |
-
results_summary: Dict[str, Any] = None) -> bool:
|
| 285 |
-
"""Update training session status"""
|
| 286 |
-
try:
|
| 287 |
-
with sqlite3.connect(self.db_path) as conn:
|
| 288 |
-
cursor = conn.cursor()
|
| 289 |
-
|
| 290 |
-
if status == 'completed':
|
| 291 |
-
cursor.execute('''
|
| 292 |
-
UPDATE medical_training_sessions
|
| 293 |
-
SET status = ?, completed_at = CURRENT_TIMESTAMP, results_summary = ?
|
| 294 |
-
WHERE session_id = ?
|
| 295 |
-
''', (status, json.dumps(results_summary) if results_summary else None, session_id))
|
| 296 |
-
else:
|
| 297 |
-
cursor.execute('''
|
| 298 |
-
UPDATE medical_training_sessions
|
| 299 |
-
SET status = ?
|
| 300 |
-
WHERE session_id = ?
|
| 301 |
-
''', (status, session_id))
|
| 302 |
-
|
| 303 |
-
conn.commit()
|
| 304 |
-
return True
|
| 305 |
-
|
| 306 |
-
except Exception as e:
|
| 307 |
-
logger.error(f"Error updating training session status: {e}")
|
| 308 |
-
return False
|
| 309 |
-
|
| 310 |
-
def get_training_history(self, user_session: str, limit: int = 10) -> List[Dict[str, Any]]:
|
| 311 |
-
"""Get training history for a user"""
|
| 312 |
-
try:
|
| 313 |
-
with sqlite3.connect(self.db_path) as conn:
|
| 314 |
-
cursor = conn.cursor()
|
| 315 |
-
|
| 316 |
-
cursor.execute('''
|
| 317 |
-
SELECT session_id, selected_datasets, training_config,
|
| 318 |
-
medical_metrics, status, created_at, completed_at, results_summary
|
| 319 |
-
FROM medical_training_sessions
|
| 320 |
-
WHERE user_session = ?
|
| 321 |
-
ORDER BY created_at DESC
|
| 322 |
-
LIMIT ?
|
| 323 |
-
''', (user_session, limit))
|
| 324 |
-
|
| 325 |
-
results = []
|
| 326 |
-
for row in cursor.fetchall():
|
| 327 |
-
session_id, datasets_json, config_json, metrics_json, status, created_at, completed_at, results_json = row
|
| 328 |
-
|
| 329 |
-
result = {
|
| 330 |
-
'session_id': session_id,
|
| 331 |
-
'selected_datasets': json.loads(datasets_json) if datasets_json else [],
|
| 332 |
-
'training_config': json.loads(config_json) if config_json else {},
|
| 333 |
-
'medical_metrics': json.loads(metrics_json) if metrics_json else {},
|
| 334 |
-
'status': status,
|
| 335 |
-
'created_at': created_at,
|
| 336 |
-
'completed_at': completed_at,
|
| 337 |
-
'results_summary': json.loads(results_json) if results_json else {}
|
| 338 |
-
}
|
| 339 |
-
results.append(result)
|
| 340 |
-
|
| 341 |
-
return results
|
| 342 |
-
|
| 343 |
-
except Exception as e:
|
| 344 |
-
logger.error(f"Error getting training history: {e}")
|
| 345 |
-
return []
|
| 346 |
-
|
| 347 |
-
def cleanup_old_selections(self, days_old: int = 30) -> int:
|
| 348 |
-
"""Clean up old inactive selections"""
|
| 349 |
-
try:
|
| 350 |
-
with sqlite3.connect(self.db_path) as conn:
|
| 351 |
-
cursor = conn.cursor()
|
| 352 |
-
|
| 353 |
-
cursor.execute('''
|
| 354 |
-
DELETE FROM medical_dataset_selections
|
| 355 |
-
WHERE is_active = FALSE
|
| 356 |
-
AND selected_at < datetime('now', '-{} days')
|
| 357 |
-
'''.format(days_old))
|
| 358 |
-
|
| 359 |
-
deleted_count = cursor.rowcount
|
| 360 |
-
conn.commit()
|
| 361 |
-
|
| 362 |
-
logger.info(f"Cleaned up {deleted_count} old dataset selections")
|
| 363 |
-
return deleted_count
|
| 364 |
-
|
| 365 |
-
except Exception as e:
|
| 366 |
-
logger.error(f"Error cleaning up old selections: {e}")
|
| 367 |
-
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/distillation.py
CHANGED
|
@@ -34,92 +34,32 @@ PROBLEMATIC_MODELS = {
|
|
| 34 |
class MultiModalDataset(Dataset):
|
| 35 |
"""
|
| 36 |
Dataset for multi-modal knowledge distillation
|
| 37 |
-
Generates
|
| 38 |
"""
|
| 39 |
-
|
| 40 |
-
def __init__(self, size: int = 1000, modalities: List[str] = None
|
| 41 |
self.size = size
|
| 42 |
self.modalities = modalities or ['text', 'vision']
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
# Create meaningful patterns instead of pure random data
|
| 46 |
-
self.text_patterns = self._create_text_patterns()
|
| 47 |
-
self.vision_patterns = self._create_vision_patterns()
|
| 48 |
-
self.audio_patterns = self._create_audio_patterns()
|
| 49 |
-
|
| 50 |
-
def _create_text_patterns(self):
|
| 51 |
-
"""Create meaningful text-like patterns"""
|
| 52 |
-
patterns = []
|
| 53 |
-
# Create different types of text patterns
|
| 54 |
-
for i in range(10):
|
| 55 |
-
# Simulate different text types (questions, statements, etc.)
|
| 56 |
-
pattern = torch.randn(512)
|
| 57 |
-
# Add some structure to make it more realistic
|
| 58 |
-
pattern[0:50] = torch.sigmoid(pattern[0:50]) # Beginning tokens
|
| 59 |
-
pattern[-50:] = torch.tanh(pattern[-50:]) # Ending tokens
|
| 60 |
-
patterns.append(pattern)
|
| 61 |
-
return patterns
|
| 62 |
-
|
| 63 |
-
def _create_vision_patterns(self):
|
| 64 |
-
"""Create meaningful vision-like patterns"""
|
| 65 |
-
patterns = []
|
| 66 |
-
for i in range(10):
|
| 67 |
-
# Create structured image-like data
|
| 68 |
-
pattern = torch.zeros(3, 224, 224)
|
| 69 |
-
# Add some geometric patterns
|
| 70 |
-
center_x, center_y = 112, 112
|
| 71 |
-
for c in range(3):
|
| 72 |
-
for x in range(224):
|
| 73 |
-
for y in range(224):
|
| 74 |
-
# Create circular patterns with noise
|
| 75 |
-
dist = ((x - center_x) ** 2 + (y - center_y) ** 2) ** 0.5
|
| 76 |
-
pattern[c, x, y] = torch.sin(dist / 20 + i) + torch.randn(1) * 0.1
|
| 77 |
-
patterns.append(pattern)
|
| 78 |
-
return patterns
|
| 79 |
-
|
| 80 |
-
def _create_audio_patterns(self):
|
| 81 |
-
"""Create meaningful audio-like patterns"""
|
| 82 |
-
patterns = []
|
| 83 |
-
for i in range(10):
|
| 84 |
-
# Create wave-like patterns
|
| 85 |
-
pattern = torch.zeros(1024)
|
| 86 |
-
for j in range(1024):
|
| 87 |
-
# Simulate audio frequencies
|
| 88 |
-
pattern[j] = torch.sin(torch.tensor(j * 0.1 + i)) + torch.randn(1) * 0.05
|
| 89 |
-
patterns.append(pattern)
|
| 90 |
-
return patterns
|
| 91 |
-
|
| 92 |
def __len__(self):
|
| 93 |
return self.size
|
| 94 |
-
|
| 95 |
def __getitem__(self, idx):
|
| 96 |
-
# Generate
|
| 97 |
data = {}
|
| 98 |
-
|
| 99 |
if 'text' in self.modalities:
|
| 100 |
-
#
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
# Add controlled noise
|
| 104 |
-
noise = torch.randn_like(base_pattern) * 0.1
|
| 105 |
-
data['text'] = base_pattern + noise
|
| 106 |
-
|
| 107 |
if 'vision' in self.modalities:
|
| 108 |
-
#
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
# Add controlled noise
|
| 112 |
-
noise = torch.randn_like(base_pattern) * 0.05
|
| 113 |
-
data['vision'] = base_pattern + noise
|
| 114 |
-
|
| 115 |
if 'audio' in self.modalities:
|
| 116 |
-
#
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
# Add controlled noise
|
| 120 |
-
noise = torch.randn_like(base_pattern) * 0.05
|
| 121 |
-
data['audio'] = base_pattern + noise
|
| 122 |
-
|
| 123 |
return data
|
| 124 |
|
| 125 |
class StudentModel(nn.Module):
|
|
@@ -296,17 +236,10 @@ class KnowledgeDistillationTrainer:
|
|
| 296 |
# Prepare teachers
|
| 297 |
teacher_models_prepared = await self._prepare_teachers(teacher_models)
|
| 298 |
|
| 299 |
-
# Create dataset and dataloader
|
| 300 |
modalities = list(student_model.modalities)
|
| 301 |
-
dataset = MultiModalDataset(
|
| 302 |
-
size=max_steps * batch_size,
|
| 303 |
-
modalities=modalities,
|
| 304 |
-
teacher_models=teacher_models_prepared
|
| 305 |
-
)
|
| 306 |
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
|
| 307 |
-
|
| 308 |
-
logger.info(f"Created dataset with {len(dataset)} samples, modalities: {modalities}")
|
| 309 |
-
logger.info(f"Training parameters - Steps: {max_steps}, LR: {learning_rate}, Batch: {batch_size}, Temp: {temperature}, Alpha: {alpha}")
|
| 310 |
|
| 311 |
# Setup optimizer and scheduler
|
| 312 |
optimizer = optim.AdamW(student_model.parameters(), lr=learning_rate, weight_decay=0.01)
|
|
@@ -314,124 +247,58 @@ class KnowledgeDistillationTrainer:
|
|
| 314 |
optimizer, num_warmup_steps=warmup_steps, num_training_steps=max_steps
|
| 315 |
)
|
| 316 |
|
| 317 |
-
# Training loop
|
| 318 |
student_model.train()
|
| 319 |
total_loss = 0.0
|
| 320 |
step = 0
|
| 321 |
-
|
| 322 |
-
loss_history = []
|
| 323 |
-
|
| 324 |
-
# Initialize step counter for loss calculation
|
| 325 |
-
self._step_count = 0
|
| 326 |
-
|
| 327 |
-
logger.info("Starting knowledge distillation training...")
|
| 328 |
-
|
| 329 |
for batch_idx, batch in enumerate(dataloader):
|
| 330 |
if step >= max_steps:
|
| 331 |
break
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
|
| 341 |
-
|
| 342 |
-
|
| 343 |
-
|
| 344 |
-
|
| 345 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
# Backward pass
|
| 377 |
-
optimizer.zero_grad()
|
| 378 |
-
distillation_loss.backward()
|
| 379 |
-
|
| 380 |
-
# Gradient clipping with monitoring
|
| 381 |
-
grad_norm = torch.nn.utils.clip_grad_norm_(student_model.parameters(), 1.0)
|
| 382 |
-
|
| 383 |
-
optimizer.step()
|
| 384 |
-
scheduler.step()
|
| 385 |
-
|
| 386 |
-
# Update metrics
|
| 387 |
-
current_loss = distillation_loss.item()
|
| 388 |
-
total_loss += current_loss
|
| 389 |
-
loss_history.append(current_loss)
|
| 390 |
-
step += 1
|
| 391 |
-
|
| 392 |
-
# Track best loss
|
| 393 |
-
if current_loss < best_loss:
|
| 394 |
-
best_loss = current_loss
|
| 395 |
-
|
| 396 |
-
# Progress callback with enhanced metrics
|
| 397 |
-
if progress_callback and step % 10 == 0:
|
| 398 |
-
avg_loss = total_loss / step
|
| 399 |
-
recent_avg = sum(loss_history[-10:]) / min(10, len(loss_history))
|
| 400 |
-
|
| 401 |
-
await progress_callback(step, max_steps, avg_loss, {
|
| 402 |
-
'learning_rate': scheduler.get_last_lr()[0],
|
| 403 |
-
'temperature': temperature,
|
| 404 |
-
'current_loss': current_loss,
|
| 405 |
-
'recent_avg_loss': recent_avg,
|
| 406 |
-
'best_loss': best_loss,
|
| 407 |
-
'grad_norm': grad_norm.item() if isinstance(grad_norm, torch.Tensor) else grad_norm
|
| 408 |
-
})
|
| 409 |
-
|
| 410 |
-
# Enhanced logging
|
| 411 |
-
if step % 50 == 0:
|
| 412 |
-
avg_loss = total_loss / step
|
| 413 |
-
recent_avg = sum(loss_history[-50:]) / min(50, len(loss_history))
|
| 414 |
-
logger.info(f"Step {step}/{max_steps} - Avg Loss: {avg_loss:.4f}, Recent Avg: {recent_avg:.4f}, "
|
| 415 |
-
f"Current: {current_loss:.4f}, Best: {best_loss:.4f}, LR: {scheduler.get_last_lr()[0]:.2e}")
|
| 416 |
-
|
| 417 |
-
except Exception as step_error:
|
| 418 |
-
logger.error(f"Error in training step {step}: {step_error}")
|
| 419 |
-
continue
|
| 420 |
-
|
| 421 |
-
# Training completion summary
|
| 422 |
-
final_avg_loss = total_loss / max(step, 1)
|
| 423 |
-
final_recent_avg = sum(loss_history[-100:]) / min(100, len(loss_history)) if loss_history else final_avg_loss
|
| 424 |
-
|
| 425 |
-
logger.info(f"Training completed successfully!")
|
| 426 |
-
logger.info(f"Total steps: {step}/{max_steps}")
|
| 427 |
-
logger.info(f"Final average loss: {final_avg_loss:.4f}")
|
| 428 |
-
logger.info(f"Recent average loss (last 100 steps): {final_recent_avg:.4f}")
|
| 429 |
-
logger.info(f"Best loss achieved: {best_loss:.4f}")
|
| 430 |
-
logger.info(f"Loss improvement: {(loss_history[0] - best_loss) / loss_history[0] * 100:.2f}%" if loss_history else "N/A")
|
| 431 |
-
|
| 432 |
-
# Store final loss for saving
|
| 433 |
-
self.final_loss = final_avg_loss
|
| 434 |
-
|
| 435 |
return student_model
|
| 436 |
|
| 437 |
except Exception as e:
|
|
@@ -454,151 +321,51 @@ class KnowledgeDistillationTrainer:
|
|
| 454 |
return prepared
|
| 455 |
|
| 456 |
async def _get_teacher_output(
|
| 457 |
-
self,
|
| 458 |
-
teacher_data: Dict[str, Any],
|
| 459 |
batch: Dict[str, torch.Tensor]
|
| 460 |
) -> torch.Tensor:
|
| 461 |
-
"""Get output from a teacher model
|
| 462 |
try:
|
| 463 |
model = teacher_data.get('model')
|
| 464 |
modality = teacher_data.get('modality', 'text')
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
else:
|
| 485 |
-
# For standard transformers
|
| 486 |
-
with torch.no_grad():
|
| 487 |
-
if input_tensor.dim() == 1:
|
| 488 |
-
input_tensor = input_tensor.unsqueeze(0)
|
| 489 |
-
|
| 490 |
-
# Try different forward methods
|
| 491 |
-
if hasattr(model, '__call__'):
|
| 492 |
-
result = model(input_tensor)
|
| 493 |
-
if hasattr(result, 'last_hidden_state'):
|
| 494 |
-
output = result.last_hidden_state.mean(dim=1)
|
| 495 |
-
elif hasattr(result, 'pooler_output'):
|
| 496 |
-
output = result.pooler_output
|
| 497 |
-
elif isinstance(result, torch.Tensor):
|
| 498 |
-
output = result
|
| 499 |
-
else:
|
| 500 |
-
output = result[0] if isinstance(result, (list, tuple)) else result
|
| 501 |
-
else:
|
| 502 |
-
output = model.forward(input_tensor)
|
| 503 |
-
|
| 504 |
-
elif modality == 'vision' and 'vision' in batch:
|
| 505 |
-
input_tensor = batch['vision']
|
| 506 |
-
|
| 507 |
-
with torch.no_grad():
|
| 508 |
-
if input_tensor.dim() == 3:
|
| 509 |
-
input_tensor = input_tensor.unsqueeze(0)
|
| 510 |
-
|
| 511 |
-
result = model(input_tensor)
|
| 512 |
-
if hasattr(result, 'last_hidden_state'):
|
| 513 |
-
output = result.last_hidden_state.mean(dim=1)
|
| 514 |
-
elif hasattr(result, 'pooler_output'):
|
| 515 |
-
output = result.pooler_output
|
| 516 |
-
elif isinstance(result, torch.Tensor):
|
| 517 |
-
output = result
|
| 518 |
-
else:
|
| 519 |
-
output = result[0] if isinstance(result, (list, tuple)) else result
|
| 520 |
-
|
| 521 |
-
else:
|
| 522 |
-
# Generate meaningful fallback based on input patterns
|
| 523 |
-
batch_size = next(iter(batch.values())).size(0)
|
| 524 |
-
output = self._generate_meaningful_output(batch, batch_size, modality)
|
| 525 |
-
|
| 526 |
-
except Exception as model_error:
|
| 527 |
-
logger.warning(f"Error calling model {model_name}: {model_error}")
|
| 528 |
-
# Generate meaningful fallback
|
| 529 |
-
batch_size = next(iter(batch.values())).size(0)
|
| 530 |
-
output = self._generate_meaningful_output(batch, batch_size, modality)
|
| 531 |
else:
|
| 532 |
-
#
|
| 533 |
batch_size = next(iter(batch.values())).size(0)
|
| 534 |
-
output =
|
| 535 |
-
|
| 536 |
-
# Ensure output is 2D (batch_size, features)
|
| 537 |
if output.dim() > 2:
|
| 538 |
output = output.view(output.size(0), -1)
|
| 539 |
elif output.dim() == 1:
|
| 540 |
output = output.unsqueeze(0)
|
| 541 |
-
|
| 542 |
-
# Move to correct device
|
| 543 |
-
output = output.to(self.device)
|
| 544 |
-
|
| 545 |
-
# Ensure reasonable output size (768 is common)
|
| 546 |
-
if output.size(-1) != 768:
|
| 547 |
-
if output.size(-1) > 768:
|
| 548 |
-
output = output[..., :768]
|
| 549 |
-
else:
|
| 550 |
-
# Pad to 768
|
| 551 |
-
padding = torch.zeros(output.size(0), 768 - output.size(-1), device=self.device)
|
| 552 |
-
output = torch.cat([output, padding], dim=-1)
|
| 553 |
-
|
| 554 |
-
logger.debug(f"Teacher output shape: {output.shape}, mean: {output.mean().item():.4f}, std: {output.std().item():.4f}")
|
| 555 |
return output
|
| 556 |
-
|
| 557 |
except Exception as e:
|
| 558 |
-
logger.
|
| 559 |
-
#
|
| 560 |
batch_size = next(iter(batch.values())).size(0)
|
| 561 |
-
return
|
| 562 |
-
|
| 563 |
-
def _generate_meaningful_output(self, batch: Dict[str, torch.Tensor], batch_size: int, modality: str) -> torch.Tensor:
|
| 564 |
-
"""Generate meaningful output based on input patterns instead of pure random"""
|
| 565 |
-
try:
|
| 566 |
-
if modality == 'text' and 'text' in batch:
|
| 567 |
-
# Generate output based on input text patterns
|
| 568 |
-
input_tensor = batch['text']
|
| 569 |
-
# Create output that correlates with input
|
| 570 |
-
output = torch.tanh(input_tensor.mean(dim=-1, keepdim=True).expand(-1, 768))
|
| 571 |
-
# Add some learned-like variation
|
| 572 |
-
output = output + torch.randn_like(output) * 0.1
|
| 573 |
-
|
| 574 |
-
elif modality == 'vision' and 'vision' in batch:
|
| 575 |
-
# Generate output based on input vision patterns
|
| 576 |
-
input_tensor = batch['vision']
|
| 577 |
-
# Extract features from image-like input
|
| 578 |
-
pooled = F.adaptive_avg_pool2d(input_tensor, (1, 1)).flatten(1)
|
| 579 |
-
# Expand to 768 dimensions
|
| 580 |
-
if pooled.size(-1) < 768:
|
| 581 |
-
repeats = 768 // pooled.size(-1) + 1
|
| 582 |
-
output = pooled.repeat(1, repeats)[:, :768]
|
| 583 |
-
else:
|
| 584 |
-
output = pooled[:, :768]
|
| 585 |
-
# Add some variation
|
| 586 |
-
output = torch.tanh(output) + torch.randn_like(output) * 0.1
|
| 587 |
-
|
| 588 |
-
else:
|
| 589 |
-
# Default meaningful pattern
|
| 590 |
-
output = torch.zeros(batch_size, 768, device=self.device)
|
| 591 |
-
for i in range(batch_size):
|
| 592 |
-
# Create different patterns for each sample
|
| 593 |
-
pattern = torch.sin(torch.arange(768, device=self.device) * 0.1 + i)
|
| 594 |
-
output[i] = pattern + torch.randn(768, device=self.device) * 0.1
|
| 595 |
-
|
| 596 |
-
return output.to(self.device)
|
| 597 |
-
|
| 598 |
-
except Exception as e:
|
| 599 |
-
logger.error(f"Error generating meaningful output: {e}")
|
| 600 |
-
# Final fallback
|
| 601 |
-
return torch.randn(batch_size, 768, device=self.device) * 0.1
|
| 602 |
|
| 603 |
def _calculate_distillation_loss(
|
| 604 |
self,
|
|
@@ -608,98 +375,42 @@ class KnowledgeDistillationTrainer:
|
|
| 608 |
alpha: float
|
| 609 |
) -> torch.Tensor:
|
| 610 |
"""
|
| 611 |
-
Calculate
|
| 612 |
-
|
| 613 |
Args:
|
| 614 |
student_output: Student model output
|
| 615 |
teacher_outputs: List of teacher outputs
|
| 616 |
temperature: Temperature for softmax
|
| 617 |
alpha: Weight for distillation loss
|
| 618 |
-
|
| 619 |
Returns:
|
| 620 |
Combined distillation loss
|
| 621 |
"""
|
| 622 |
if not teacher_outputs:
|
| 623 |
-
|
| 624 |
-
|
| 625 |
-
|
| 626 |
-
|
| 627 |
-
|
| 628 |
-
|
| 629 |
-
|
| 630 |
-
|
| 631 |
-
|
| 632 |
-
|
| 633 |
-
|
| 634 |
-
|
| 635 |
-
|
| 636 |
-
|
| 637 |
-
|
| 638 |
-
|
| 639 |
-
|
| 640 |
-
|
| 641 |
-
|
| 642 |
-
|
| 643 |
-
|
| 644 |
-
|
| 645 |
-
|
| 646 |
-
|
| 647 |
-
min_dim = min(student_output.size(-1), teacher_ensemble.size(-1))
|
| 648 |
-
student_logits = student_output[..., :min_dim]
|
| 649 |
-
teacher_logits = teacher_ensemble[..., :min_dim]
|
| 650 |
-
|
| 651 |
-
# Add small epsilon for numerical stability
|
| 652 |
-
eps = 1e-8
|
| 653 |
-
student_logits = student_logits + eps
|
| 654 |
-
teacher_logits = teacher_logits + eps
|
| 655 |
-
|
| 656 |
-
# Normalize logits to prevent overflow
|
| 657 |
-
student_logits = student_logits / (torch.norm(student_logits, dim=-1, keepdim=True) + eps)
|
| 658 |
-
teacher_logits = teacher_logits / (torch.norm(teacher_logits, dim=-1, keepdim=True) + eps)
|
| 659 |
-
|
| 660 |
-
# Temperature-scaled softmax with improved numerical stability
|
| 661 |
-
student_soft = F.log_softmax(student_logits / temperature, dim=-1)
|
| 662 |
-
teacher_soft = F.softmax(teacher_logits / temperature, dim=-1)
|
| 663 |
-
|
| 664 |
-
# KL divergence loss with numerical stability
|
| 665 |
-
kl_loss = F.kl_div(student_soft, teacher_soft, reduction='batchmean')
|
| 666 |
-
|
| 667 |
-
# MSE loss for feature matching
|
| 668 |
-
mse_loss = F.mse_loss(student_logits, teacher_logits)
|
| 669 |
-
|
| 670 |
-
# Cosine similarity loss (encourages similar directions)
|
| 671 |
-
cos_sim = F.cosine_similarity(student_logits, teacher_logits, dim=-1)
|
| 672 |
-
cos_loss = 1.0 - cos_sim.mean()
|
| 673 |
-
|
| 674 |
-
# L1 loss for sparsity
|
| 675 |
-
l1_loss = F.l1_loss(student_logits, teacher_logits)
|
| 676 |
-
|
| 677 |
-
# Combine losses with adaptive weighting
|
| 678 |
-
total_loss = (
|
| 679 |
-
alpha * kl_loss + # Knowledge distillation
|
| 680 |
-
(1 - alpha) * 0.4 * mse_loss + # Feature matching
|
| 681 |
-
(1 - alpha) * 0.3 * cos_loss + # Direction alignment
|
| 682 |
-
(1 - alpha) * 0.3 * l1_loss # Sparsity
|
| 683 |
-
)
|
| 684 |
-
|
| 685 |
-
# Ensure loss is positive and reasonable
|
| 686 |
-
total_loss = torch.clamp(total_loss, min=0.001, max=10.0)
|
| 687 |
-
|
| 688 |
-
# Log detailed loss components for debugging
|
| 689 |
-
if hasattr(self, '_step_count'):
|
| 690 |
-
self._step_count += 1
|
| 691 |
-
if self._step_count % 50 == 0:
|
| 692 |
-
logger.debug(f"Loss components - KL: {kl_loss.item():.4f}, MSE: {mse_loss.item():.4f}, "
|
| 693 |
-
f"Cos: {cos_loss.item():.4f}, L1: {l1_loss.item():.4f}, Total: {total_loss.item():.4f}")
|
| 694 |
-
else:
|
| 695 |
-
self._step_count = 1
|
| 696 |
-
|
| 697 |
-
return total_loss
|
| 698 |
-
|
| 699 |
-
except Exception as e:
|
| 700 |
-
logger.error(f"Error calculating distillation loss: {e}")
|
| 701 |
-
# Return a meaningful fallback loss
|
| 702 |
-
return torch.tensor(0.5, device=self.device, requires_grad=True)
|
| 703 |
|
| 704 |
async def save_model(self, model: StudentModel, save_path: str, training_metadata: Dict[str, Any] = None) -> None:
|
| 705 |
"""
|
|
|
|
| 34 |
class MultiModalDataset(Dataset):
|
| 35 |
"""
|
| 36 |
Dataset for multi-modal knowledge distillation
|
| 37 |
+
Generates synthetic data for different modalities
|
| 38 |
"""
|
| 39 |
+
|
| 40 |
+
def __init__(self, size: int = 1000, modalities: List[str] = None):
|
| 41 |
self.size = size
|
| 42 |
self.modalities = modalities or ['text', 'vision']
|
| 43 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
def __len__(self):
|
| 45 |
return self.size
|
| 46 |
+
|
| 47 |
def __getitem__(self, idx):
|
| 48 |
+
# Generate synthetic data based on modalities
|
| 49 |
data = {}
|
| 50 |
+
|
| 51 |
if 'text' in self.modalities:
|
| 52 |
+
# Generate random text-like embeddings
|
| 53 |
+
data['text'] = torch.randn(512) # Common embedding size
|
| 54 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
if 'vision' in self.modalities:
|
| 56 |
+
# Generate random image-like tensors
|
| 57 |
+
data['vision'] = torch.randn(3, 224, 224) # Standard image size
|
| 58 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
if 'audio' in self.modalities:
|
| 60 |
+
# Generate random audio-like features
|
| 61 |
+
data['audio'] = torch.randn(1024)
|
| 62 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
return data
|
| 64 |
|
| 65 |
class StudentModel(nn.Module):
|
|
|
|
| 236 |
# Prepare teachers
|
| 237 |
teacher_models_prepared = await self._prepare_teachers(teacher_models)
|
| 238 |
|
| 239 |
+
# Create dataset and dataloader
|
| 240 |
modalities = list(student_model.modalities)
|
| 241 |
+
dataset = MultiModalDataset(size=max_steps * batch_size, modalities=modalities)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 242 |
dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
|
|
|
|
|
|
|
|
|
|
| 243 |
|
| 244 |
# Setup optimizer and scheduler
|
| 245 |
optimizer = optim.AdamW(student_model.parameters(), lr=learning_rate, weight_decay=0.01)
|
|
|
|
| 247 |
optimizer, num_warmup_steps=warmup_steps, num_training_steps=max_steps
|
| 248 |
)
|
| 249 |
|
| 250 |
+
# Training loop
|
| 251 |
student_model.train()
|
| 252 |
total_loss = 0.0
|
| 253 |
step = 0
|
| 254 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 255 |
for batch_idx, batch in enumerate(dataloader):
|
| 256 |
if step >= max_steps:
|
| 257 |
break
|
| 258 |
+
|
| 259 |
+
# Move batch to device
|
| 260 |
+
batch = {k: v.to(self.device) for k, v in batch.items()}
|
| 261 |
+
|
| 262 |
+
# Forward pass through student
|
| 263 |
+
student_output = student_model(batch)
|
| 264 |
+
|
| 265 |
+
# Get teacher outputs
|
| 266 |
+
teacher_outputs = []
|
| 267 |
+
for teacher_data in teacher_models_prepared:
|
| 268 |
+
with torch.no_grad():
|
| 269 |
+
teacher_output = await self._get_teacher_output(teacher_data, batch)
|
| 270 |
+
teacher_outputs.append(teacher_output)
|
| 271 |
+
|
| 272 |
+
# Calculate distillation loss
|
| 273 |
+
distillation_loss = self._calculate_distillation_loss(
|
| 274 |
+
student_output, teacher_outputs, temperature, alpha
|
| 275 |
+
)
|
| 276 |
+
|
| 277 |
+
# Backward pass
|
| 278 |
+
optimizer.zero_grad()
|
| 279 |
+
distillation_loss.backward()
|
| 280 |
+
torch.nn.utils.clip_grad_norm_(student_model.parameters(), 1.0)
|
| 281 |
+
optimizer.step()
|
| 282 |
+
scheduler.step()
|
| 283 |
+
|
| 284 |
+
# Update metrics
|
| 285 |
+
total_loss += distillation_loss.item()
|
| 286 |
+
step += 1
|
| 287 |
+
|
| 288 |
+
# Progress callback
|
| 289 |
+
if progress_callback and step % 10 == 0:
|
| 290 |
+
avg_loss = total_loss / step
|
| 291 |
+
await progress_callback(step, max_steps, avg_loss, {
|
| 292 |
+
'learning_rate': scheduler.get_last_lr()[0],
|
| 293 |
+
'temperature': temperature
|
| 294 |
+
})
|
| 295 |
+
|
| 296 |
+
# Log progress
|
| 297 |
+
if step % 100 == 0:
|
| 298 |
+
avg_loss = total_loss / step
|
| 299 |
+
logger.info(f"Step {step}/{max_steps}, Loss: {avg_loss:.4f}")
|
| 300 |
+
|
| 301 |
+
logger.info(f"Training completed. Final loss: {total_loss / max_steps:.4f}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
return student_model
|
| 303 |
|
| 304 |
except Exception as e:
|
|
|
|
| 321 |
return prepared
|
| 322 |
|
| 323 |
async def _get_teacher_output(
|
| 324 |
+
self,
|
| 325 |
+
teacher_data: Dict[str, Any],
|
| 326 |
batch: Dict[str, torch.Tensor]
|
| 327 |
) -> torch.Tensor:
|
| 328 |
+
"""Get output from a teacher model"""
|
| 329 |
try:
|
| 330 |
model = teacher_data.get('model')
|
| 331 |
modality = teacher_data.get('modality', 'text')
|
| 332 |
+
|
| 333 |
+
# Simple output generation based on modality
|
| 334 |
+
if modality == 'text' and 'text' in batch:
|
| 335 |
+
# For text models, return embedding-like output
|
| 336 |
+
input_tensor = batch['text']
|
| 337 |
+
if hasattr(model, 'forward'):
|
| 338 |
+
output = model(input_tensor.unsqueeze(0) if input_tensor.dim() == 1 else input_tensor)
|
| 339 |
+
else:
|
| 340 |
+
# Fallback for non-standard models
|
| 341 |
+
output = torch.randn(input_tensor.size(0), 768, device=self.device)
|
| 342 |
+
|
| 343 |
+
elif modality == 'vision' and 'vision' in batch:
|
| 344 |
+
# For vision models
|
| 345 |
+
input_tensor = batch['vision']
|
| 346 |
+
if hasattr(model, 'forward'):
|
| 347 |
+
output = model(input_tensor.unsqueeze(0) if input_tensor.dim() == 3 else input_tensor)
|
| 348 |
+
else:
|
| 349 |
+
output = torch.randn(input_tensor.size(0), 768, device=self.device)
|
| 350 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 351 |
else:
|
| 352 |
+
# Default fallback
|
| 353 |
batch_size = next(iter(batch.values())).size(0)
|
| 354 |
+
output = torch.randn(batch_size, 768, device=self.device)
|
| 355 |
+
|
| 356 |
+
# Ensure output is 2D (batch_size, features)
|
| 357 |
if output.dim() > 2:
|
| 358 |
output = output.view(output.size(0), -1)
|
| 359 |
elif output.dim() == 1:
|
| 360 |
output = output.unsqueeze(0)
|
| 361 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 362 |
return output
|
| 363 |
+
|
| 364 |
except Exception as e:
|
| 365 |
+
logger.warning(f"Error getting teacher output: {e}")
|
| 366 |
+
# Return random output as fallback
|
| 367 |
batch_size = next(iter(batch.values())).size(0)
|
| 368 |
+
return torch.randn(batch_size, 768, device=self.device)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 369 |
|
| 370 |
def _calculate_distillation_loss(
|
| 371 |
self,
|
|
|
|
| 375 |
alpha: float
|
| 376 |
) -> torch.Tensor:
|
| 377 |
"""
|
| 378 |
+
Calculate knowledge distillation loss
|
| 379 |
+
|
| 380 |
Args:
|
| 381 |
student_output: Student model output
|
| 382 |
teacher_outputs: List of teacher outputs
|
| 383 |
temperature: Temperature for softmax
|
| 384 |
alpha: Weight for distillation loss
|
| 385 |
+
|
| 386 |
Returns:
|
| 387 |
Combined distillation loss
|
| 388 |
"""
|
| 389 |
if not teacher_outputs:
|
| 390 |
+
return torch.tensor(0.0, device=self.device, requires_grad=True)
|
| 391 |
+
|
| 392 |
+
# Ensemble teacher outputs (average)
|
| 393 |
+
teacher_ensemble = torch.stack(teacher_outputs).mean(dim=0)
|
| 394 |
+
|
| 395 |
+
# Ensure same dimensions
|
| 396 |
+
min_dim = min(student_output.size(-1), teacher_ensemble.size(-1))
|
| 397 |
+
student_logits = student_output[..., :min_dim]
|
| 398 |
+
teacher_logits = teacher_ensemble[..., :min_dim]
|
| 399 |
+
|
| 400 |
+
# Temperature-scaled softmax
|
| 401 |
+
student_soft = F.log_softmax(student_logits / temperature, dim=-1)
|
| 402 |
+
teacher_soft = F.softmax(teacher_logits / temperature, dim=-1)
|
| 403 |
+
|
| 404 |
+
# KL divergence loss
|
| 405 |
+
distillation_loss = F.kl_div(student_soft, teacher_soft, reduction='batchmean')
|
| 406 |
+
|
| 407 |
+
# Optional: Add MSE loss for feature matching
|
| 408 |
+
feature_loss = F.mse_loss(student_logits, teacher_logits)
|
| 409 |
+
|
| 410 |
+
# Combine losses
|
| 411 |
+
total_loss = alpha * distillation_loss + (1 - alpha) * feature_loss
|
| 412 |
+
|
| 413 |
+
return total_loss
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 414 |
|
| 415 |
async def save_model(self, model: StudentModel, save_path: str, training_metadata: Dict[str, Any] = None) -> None:
|
| 416 |
"""
|
src/medical/medical_config.py
DELETED
|
@@ -1,258 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
Medical AI Platform Configuration
|
| 3 |
-
Contains all medical-specific configurations and constants
|
| 4 |
-
"""
|
| 5 |
-
|
| 6 |
-
from typing import Dict, List, Any
|
| 7 |
-
|
| 8 |
-
# Supported Medical Datasets
|
| 9 |
-
SUPPORTED_MEDICAL_DATASETS = {
|
| 10 |
-
'roco_v2': {
|
| 11 |
-
'name': 'ROCOv2 Radiology',
|
| 12 |
-
'repo_id': 'eltorio/ROCOv2-radiology',
|
| 13 |
-
'description': 'صور شعاعية مع تقارير طبية مفصلة - مجموعة بيانات شاملة للأشعة الطبية',
|
| 14 |
-
'modalities': ['radiology', 'text'],
|
| 15 |
-
'size_gb': 8.5,
|
| 16 |
-
'num_samples': 81000,
|
| 17 |
-
'languages': ['en', 'ar'],
|
| 18 |
-
'medical_specialties': ['radiology', 'general'],
|
| 19 |
-
'data_format': 'image_text_pairs',
|
| 20 |
-
'streaming_supported': True,
|
| 21 |
-
'recommended_for': ['تدريب نماذج التشخيص الإشعاعي', 'تحليل الصور الطبية', 'النماذج متعددة الوسائط'],
|
| 22 |
-
'difficulty_level': 'متوسط',
|
| 23 |
-
'quality_score': 9.2,
|
| 24 |
-
'last_updated': '2024-01',
|
| 25 |
-
'license': 'CC BY 4.0'
|
| 26 |
-
},
|
| 27 |
-
'ct_rate': {
|
| 28 |
-
'name': 'CT-RATE',
|
| 29 |
-
'repo_id': 'ibrahimhamamci/CT-RATE',
|
| 30 |
-
'description': 'صور CT مع تقييمات وتشخيصات - بيانات متخصصة للأشعة المقطعية',
|
| 31 |
-
'modalities': ['ct_scan', 'text'],
|
| 32 |
-
'size_gb': 12.3,
|
| 33 |
-
'num_samples': 50000,
|
| 34 |
-
'languages': ['en'],
|
| 35 |
-
'medical_specialties': ['radiology', 'emergency', 'internal_medicine'],
|
| 36 |
-
'data_format': 'image_text_pairs',
|
| 37 |
-
'streaming_supported': True,
|
| 38 |
-
'recommended_for': ['تشخيص الأشعة المقطعية', 'الطب الطارئ', 'التشخيص السريع'],
|
| 39 |
-
'difficulty_level': 'متقدم',
|
| 40 |
-
'quality_score': 8.8,
|
| 41 |
-
'last_updated': '2024-02',
|
| 42 |
-
'license': 'MIT'
|
| 43 |
-
},
|
| 44 |
-
'umie_datasets': {
|
| 45 |
-
'name': 'UMIE Medical Datasets',
|
| 46 |
-
'repo_id': 'lion-ai/umie_datasets',
|
| 47 |
-
'description': 'بيانات طبية متنوعة ومتعددة الوسائط - مجموعة شاملة للتطبيقات الطبية المختلفة',
|
| 48 |
-
'modalities': ['multimodal', 'text', 'imaging'],
|
| 49 |
-
'size_gb': 15.7,
|
| 50 |
-
'num_samples': 120000,
|
| 51 |
-
'languages': ['en', 'ar', 'fr'],
|
| 52 |
-
'medical_specialties': ['general', 'cardiology', 'neurology', 'oncology'],
|
| 53 |
-
'data_format': 'multimodal',
|
| 54 |
-
'streaming_supported': True,
|
| 55 |
-
'recommended_for': ['النماذج العامة', 'التخصصات المتعددة', 'البحث الطبي'],
|
| 56 |
-
'difficulty_level': 'متقدم',
|
| 57 |
-
'quality_score': 9.5,
|
| 58 |
-
'last_updated': '2024-03',
|
| 59 |
-
'license': 'Apache 2.0'
|
| 60 |
-
}
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
# Medical Specialties
|
| 64 |
-
MEDICAL_SPECIALTIES = {
|
| 65 |
-
'radiology': {
|
| 66 |
-
'name': 'الأشعة الطبية',
|
| 67 |
-
'description': 'تشخيص الأمراض باستخدام التصوير الطبي',
|
| 68 |
-
'common_modalities': ['X-ray', 'CT', 'MRI', 'Ultrasound'],
|
| 69 |
-
'datasets': ['roco_v2', 'ct_rate'],
|
| 70 |
-
'difficulty': 'متوسط إلى متقدم'
|
| 71 |
-
},
|
| 72 |
-
'cardiology': {
|
| 73 |
-
'name': 'أمراض القلب',
|
| 74 |
-
'description': 'تشخيص وعلاج أمراض القلب والأوعية الدموية',
|
| 75 |
-
'common_modalities': ['ECG', 'Echocardiogram', 'Cardiac_CT'],
|
| 76 |
-
'datasets': ['umie_datasets'],
|
| 77 |
-
'difficulty': 'متقدم'
|
| 78 |
-
},
|
| 79 |
-
'neurology': {
|
| 80 |
-
'name': 'الأمراض العصبية',
|
| 81 |
-
'description': 'تشخيص وعلاج اضطرابات الجهاز العصبي',
|
| 82 |
-
'common_modalities': ['Brain_MRI', 'EEG', 'CT_Brain'],
|
| 83 |
-
'datasets': ['umie_datasets'],
|
| 84 |
-
'difficulty': 'متقدم'
|
| 85 |
-
},
|
| 86 |
-
'oncology': {
|
| 87 |
-
'name': 'علم الأورام',
|
| 88 |
-
'description': 'تشخيص وعلاج السرطان',
|
| 89 |
-
'common_modalities': ['CT', 'MRI', 'PET_Scan'],
|
| 90 |
-
'datasets': ['umie_datasets'],
|
| 91 |
-
'difficulty': 'متقدم جداً'
|
| 92 |
-
},
|
| 93 |
-
'emergency': {
|
| 94 |
-
'name': 'الطب الطارئ',
|
| 95 |
-
'description': 'التشخيص السريع في حالات الطوارئ',
|
| 96 |
-
'common_modalities': ['X-ray', 'CT', 'Ultrasound'],
|
| 97 |
-
'datasets': ['ct_rate'],
|
| 98 |
-
'difficulty': 'متوسط'
|
| 99 |
-
},
|
| 100 |
-
'general': {
|
| 101 |
-
'name': 'الطب العام',
|
| 102 |
-
'description': 'التشخيص العام والرعاية الأولية',
|
| 103 |
-
'common_modalities': ['X-ray', 'Basic_Imaging'],
|
| 104 |
-
'datasets': ['roco_v2', 'umie_datasets'],
|
| 105 |
-
'difficulty': 'مبتدئ إلى متوسط'
|
| 106 |
-
}
|
| 107 |
-
}
|
| 108 |
-
|
| 109 |
-
# Training Configurations for Medical Data
|
| 110 |
-
MEDICAL_TRAINING_CONFIGS = {
|
| 111 |
-
'beginner': {
|
| 112 |
-
'name': 'مبتدئ',
|
| 113 |
-
'max_steps': 500,
|
| 114 |
-
'batch_size': 2,
|
| 115 |
-
'learning_rate': 5e-5,
|
| 116 |
-
'recommended_datasets': ['roco_v2'],
|
| 117 |
-
'description': 'إعدادات للمبتدئين في التدريب الطبي'
|
| 118 |
-
},
|
| 119 |
-
'intermediate': {
|
| 120 |
-
'name': 'متوسط',
|
| 121 |
-
'max_steps': 1000,
|
| 122 |
-
'batch_size': 4,
|
| 123 |
-
'learning_rate': 1e-4,
|
| 124 |
-
'recommended_datasets': ['roco_v2', 'ct_rate'],
|
| 125 |
-
'description': 'إعدادات متوسطة للتدريب المتقدم'
|
| 126 |
-
},
|
| 127 |
-
'advanced': {
|
| 128 |
-
'name': 'متقدم',
|
| 129 |
-
'max_steps': 2000,
|
| 130 |
-
'batch_size': 6,
|
| 131 |
-
'learning_rate': 1e-4,
|
| 132 |
-
'recommended_datasets': ['ct_rate', 'umie_datasets'],
|
| 133 |
-
'description': 'إعدادات متقدمة للخبراء'
|
| 134 |
-
},
|
| 135 |
-
'research': {
|
| 136 |
-
'name': 'بحثي',
|
| 137 |
-
'max_steps': 5000,
|
| 138 |
-
'batch_size': 8,
|
| 139 |
-
'learning_rate': 5e-5,
|
| 140 |
-
'recommended_datasets': ['umie_datasets'],
|
| 141 |
-
'description': 'إعدادات للبحث العلمي المتقدم'
|
| 142 |
-
}
|
| 143 |
-
}
|
| 144 |
-
|
| 145 |
-
# Medical Data Processing Settings
|
| 146 |
-
MEDICAL_DATA_SETTINGS = {
|
| 147 |
-
'image_processing': {
|
| 148 |
-
'max_image_size': (512, 512),
|
| 149 |
-
'supported_formats': ['DICOM', 'PNG', 'JPEG', 'TIFF'],
|
| 150 |
-
'normalization': 'hounsfield_units',
|
| 151 |
-
'augmentation_enabled': True
|
| 152 |
-
},
|
| 153 |
-
'text_processing': {
|
| 154 |
-
'max_text_length': 512,
|
| 155 |
-
'supported_languages': ['ar', 'en', 'fr'],
|
| 156 |
-
'medical_terminology_support': True,
|
| 157 |
-
'anonymization_required': True
|
| 158 |
-
},
|
| 159 |
-
'memory_optimization': {
|
| 160 |
-
'streaming_threshold_gb': 4.0,
|
| 161 |
-
'batch_size_auto_adjust': True,
|
| 162 |
-
'garbage_collection_frequency': 100,
|
| 163 |
-
'memory_warning_threshold': 0.8
|
| 164 |
-
}
|
| 165 |
-
}
|
| 166 |
-
|
| 167 |
-
# Quality Metrics for Medical Models
|
| 168 |
-
MEDICAL_QUALITY_METRICS = {
|
| 169 |
-
'diagnostic_accuracy': {
|
| 170 |
-
'name': 'دقة التشخيص',
|
| 171 |
-
'target_threshold': 0.95,
|
| 172 |
-
'critical_threshold': 0.90,
|
| 173 |
-
'description': 'نسبة التشخيصات الصحيحة'
|
| 174 |
-
},
|
| 175 |
-
'sensitivity': {
|
| 176 |
-
'name': 'الحساسية',
|
| 177 |
-
'target_threshold': 0.90,
|
| 178 |
-
'critical_threshold': 0.85,
|
| 179 |
-
'description': 'قدرة النموذج على اكتشاف الحالات الإيجابية'
|
| 180 |
-
},
|
| 181 |
-
'specificity': {
|
| 182 |
-
'name': 'النوعية',
|
| 183 |
-
'target_threshold': 0.95,
|
| 184 |
-
'critical_threshold': 0.90,
|
| 185 |
-
'description': 'قدرة النموذج على تجنب الإيجابيات الكاذبة'
|
| 186 |
-
},
|
| 187 |
-
'f1_score': {
|
| 188 |
-
'name': 'نتيجة F1',
|
| 189 |
-
'target_threshold': 0.92,
|
| 190 |
-
'critical_threshold': 0.88,
|
| 191 |
-
'description': 'المتوسط التوافقي للدقة والاستدعاء'
|
| 192 |
-
}
|
| 193 |
-
}
|
| 194 |
-
|
| 195 |
-
# Default Medical Training Parameters
|
| 196 |
-
DEFAULT_MEDICAL_TRAINING_PARAMS = {
|
| 197 |
-
'max_steps': 1000,
|
| 198 |
-
'learning_rate': 1e-4,
|
| 199 |
-
'batch_size': 4,
|
| 200 |
-
'temperature': 3.0, # Lower temperature for medical precision
|
| 201 |
-
'alpha': 0.8, # Higher weight for knowledge distillation
|
| 202 |
-
'warmup_steps': 100,
|
| 203 |
-
'weight_decay': 0.01,
|
| 204 |
-
'gradient_clipping': 1.0,
|
| 205 |
-
'evaluation_frequency': 50,
|
| 206 |
-
'save_frequency': 200,
|
| 207 |
-
'early_stopping_patience': 5,
|
| 208 |
-
'medical_validation_enabled': True
|
| 209 |
-
}
|
| 210 |
-
|
| 211 |
-
def get_dataset_by_specialty(specialty: str) -> List[str]:
|
| 212 |
-
"""Get recommended datasets for a medical specialty"""
|
| 213 |
-
if specialty in MEDICAL_SPECIALTIES:
|
| 214 |
-
return MEDICAL_SPECIALTIES[specialty]['datasets']
|
| 215 |
-
return []
|
| 216 |
-
|
| 217 |
-
def get_training_config_for_level(level: str) -> Dict[str, Any]:
|
| 218 |
-
"""Get training configuration for experience level"""
|
| 219 |
-
if level in MEDICAL_TRAINING_CONFIGS:
|
| 220 |
-
return MEDICAL_TRAINING_CONFIGS[level].copy()
|
| 221 |
-
return MEDICAL_TRAINING_CONFIGS['intermediate'].copy()
|
| 222 |
-
|
| 223 |
-
def validate_medical_dataset_selection(selected_datasets: List[str]) -> Dict[str, Any]:
|
| 224 |
-
"""Validate selected medical datasets"""
|
| 225 |
-
validation_result = {
|
| 226 |
-
'valid': True,
|
| 227 |
-
'warnings': [],
|
| 228 |
-
'errors': [],
|
| 229 |
-
'total_size_gb': 0,
|
| 230 |
-
'total_samples': 0,
|
| 231 |
-
'specialties_covered': set(),
|
| 232 |
-
'modalities_covered': set()
|
| 233 |
-
}
|
| 234 |
-
|
| 235 |
-
for dataset_name in selected_datasets:
|
| 236 |
-
if dataset_name not in SUPPORTED_MEDICAL_DATASETS:
|
| 237 |
-
validation_result['errors'].append(f"Dataset غير مدعوم: {dataset_name}")
|
| 238 |
-
validation_result['valid'] = False
|
| 239 |
-
continue
|
| 240 |
-
|
| 241 |
-
dataset_info = SUPPORTED_MEDICAL_DATASETS[dataset_name]
|
| 242 |
-
validation_result['total_size_gb'] += dataset_info['size_gb']
|
| 243 |
-
validation_result['total_samples'] += dataset_info['num_samples']
|
| 244 |
-
validation_result['specialties_covered'].update(dataset_info['medical_specialties'])
|
| 245 |
-
validation_result['modalities_covered'].update(dataset_info['modalities'])
|
| 246 |
-
|
| 247 |
-
# Check for warnings
|
| 248 |
-
if validation_result['total_size_gb'] > 20:
|
| 249 |
-
validation_result['warnings'].append("حجم البيانات كبير جداً - قد يتطلب ذاكرة إضافية")
|
| 250 |
-
|
| 251 |
-
if len(validation_result['specialties_covered']) > 3:
|
| 252 |
-
validation_result['warnings'].append("تخصصات متعددة - قد يؤثر على دقة النموذج")
|
| 253 |
-
|
| 254 |
-
# Convert sets to lists for JSON serialization
|
| 255 |
-
validation_result['specialties_covered'] = list(validation_result['specialties_covered'])
|
| 256 |
-
validation_result['modalities_covered'] = list(validation_result['modalities_covered'])
|
| 257 |
-
|
| 258 |
-
return validation_result
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static/js/medical-datasets.js
CHANGED
|
@@ -1,370 +1,63 @@
|
|
| 1 |
/**
|
| 2 |
* Medical Datasets Manager JavaScript
|
| 3 |
-
* Handles medical datasets functionality
|
| 4 |
*/
|
| 5 |
|
| 6 |
class MedicalDatasetsManager {
|
| 7 |
constructor() {
|
| 8 |
-
this.datasets =
|
| 9 |
-
this.
|
| 10 |
-
this.
|
| 11 |
-
this.userSession = this.generateUserSession();
|
| 12 |
-
this.userPreferences = {};
|
| 13 |
this.init();
|
| 14 |
}
|
| 15 |
|
| 16 |
-
generateUserSession() {
|
| 17 |
-
// Generate or retrieve user session ID
|
| 18 |
-
let sessionId = localStorage.getItem('medical_user_session');
|
| 19 |
-
if (!sessionId) {
|
| 20 |
-
sessionId = 'medical_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
| 21 |
-
localStorage.setItem('medical_user_session', sessionId);
|
| 22 |
-
}
|
| 23 |
-
return sessionId;
|
| 24 |
-
}
|
| 25 |
-
|
| 26 |
init() {
|
| 27 |
this.loadDatasets();
|
| 28 |
-
this.
|
| 29 |
this.setupEventListeners();
|
| 30 |
-
|
| 31 |
-
//
|
| 32 |
-
setInterval(() => this.
|
| 33 |
}
|
| 34 |
|
| 35 |
setupEventListeners() {
|
| 36 |
-
//
|
| 37 |
-
document.addEventListener('
|
| 38 |
-
|
| 39 |
-
this.handleDatasetSelection(e.target);
|
| 40 |
-
}
|
| 41 |
-
});
|
| 42 |
-
|
| 43 |
-
// Add event listeners for specialty filters
|
| 44 |
-
document.addEventListener('change', (e) => {
|
| 45 |
-
if (e.target.classList.contains('specialty-filter')) {
|
| 46 |
-
this.handleSpecialtyFilter();
|
| 47 |
-
}
|
| 48 |
});
|
| 49 |
-
|
| 50 |
-
// Save selections button
|
| 51 |
-
const saveBtn = document.getElementById('save-selections-btn');
|
| 52 |
-
if (saveBtn) {
|
| 53 |
-
saveBtn.addEventListener('click', () => this.saveSelections());
|
| 54 |
-
}
|
| 55 |
-
|
| 56 |
-
// Clear selections button
|
| 57 |
-
const clearBtn = document.getElementById('clear-selections-btn');
|
| 58 |
-
if (clearBtn) {
|
| 59 |
-
clearBtn.addEventListener('click', () => this.clearSelections());
|
| 60 |
-
}
|
| 61 |
}
|
| 62 |
|
| 63 |
async loadDatasets() {
|
| 64 |
try {
|
| 65 |
-
this.showLoading();
|
| 66 |
-
|
| 67 |
const response = await fetch('/api/medical-datasets');
|
| 68 |
const data = await response.json();
|
| 69 |
-
|
| 70 |
-
if (response.ok
|
| 71 |
this.datasets = data.datasets;
|
| 72 |
-
this.specialties = data.specialties;
|
| 73 |
this.renderDatasets();
|
| 74 |
-
this.renderSpecialtyFilters();
|
| 75 |
} else {
|
| 76 |
-
this.showError('فشل في تحميل قواعد
|
| 77 |
}
|
| 78 |
} catch (error) {
|
| 79 |
console.error('Error loading datasets:', error);
|
| 80 |
this.showError('خطأ في الاتصال بالخادم');
|
| 81 |
-
} finally {
|
| 82 |
-
this.hideLoading();
|
| 83 |
}
|
| 84 |
}
|
| 85 |
|
| 86 |
-
async
|
| 87 |
try {
|
| 88 |
-
const response = await fetch(
|
| 89 |
const data = await response.json();
|
| 90 |
-
|
| 91 |
-
if (response.ok && data.success) {
|
| 92 |
-
// Load previous selections
|
| 93 |
-
this.selectedDatasets.clear();
|
| 94 |
-
data.selections.forEach(selection => {
|
| 95 |
-
this.selectedDatasets.add(selection.dataset_name);
|
| 96 |
-
});
|
| 97 |
-
|
| 98 |
-
this.userPreferences = data.preferences;
|
| 99 |
-
this.updateSelectionUI();
|
| 100 |
-
}
|
| 101 |
-
} catch (error) {
|
| 102 |
-
console.error('Error loading user selections:', error);
|
| 103 |
-
}
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
renderDatasets() {
|
| 107 |
-
const grid = document.getElementById('datasets-grid');
|
| 108 |
-
if (!grid) return;
|
| 109 |
-
|
| 110 |
-
let html = '';
|
| 111 |
-
|
| 112 |
-
// Add selection controls
|
| 113 |
-
html += `
|
| 114 |
-
<div class="col-12 mb-4">
|
| 115 |
-
<div class="card">
|
| 116 |
-
<div class="card-header">
|
| 117 |
-
<h5><i class="fas fa-filter me-2"></i>تصفية وإدارة البيانات</h5>
|
| 118 |
-
</div>
|
| 119 |
-
<div class="card-body">
|
| 120 |
-
<div class="row">
|
| 121 |
-
<div class="col-md-6">
|
| 122 |
-
<label class="form-label">تصفية حسب التخصص:</label>
|
| 123 |
-
<div id="specialty-filters"></div>
|
| 124 |
-
</div>
|
| 125 |
-
<div class="col-md-6">
|
| 126 |
-
<label class="form-label">البيانات المختارة:</label>
|
| 127 |
-
<div id="selected-summary" class="alert alert-info">
|
| 128 |
-
لم يتم اختيار أي قواعد بيانات بعد
|
| 129 |
-
</div>
|
| 130 |
-
<div class="btn-group w-100">
|
| 131 |
-
<button id="save-selections-btn" class="btn btn-success">
|
| 132 |
-
<i class="fas fa-save me-2"></i>حفظ الاختيارات
|
| 133 |
-
</button>
|
| 134 |
-
<button id="clear-selections-btn" class="btn btn-outline-danger">
|
| 135 |
-
<i class="fas fa-trash me-2"></i>مسح الكل
|
| 136 |
-
</button>
|
| 137 |
-
</div>
|
| 138 |
-
</div>
|
| 139 |
-
</div>
|
| 140 |
-
</div>
|
| 141 |
-
</div>
|
| 142 |
-
</div>
|
| 143 |
-
`;
|
| 144 |
-
|
| 145 |
-
// Render dataset cards
|
| 146 |
-
Object.entries(this.datasets).forEach(([key, dataset]) => {
|
| 147 |
-
const isSelected = this.selectedDatasets.has(key);
|
| 148 |
-
|
| 149 |
-
html += `
|
| 150 |
-
<div class="col-lg-6 col-xl-4 mb-4 dataset-item" data-specialties="${dataset.medical_specialties.join(',')}">
|
| 151 |
-
<div class="dataset-card ${isSelected ? 'border-success' : ''}">
|
| 152 |
-
<div class="d-flex justify-content-between align-items-start mb-3">
|
| 153 |
-
<div class="form-check">
|
| 154 |
-
<input class="form-check-input dataset-checkbox" type="checkbox"
|
| 155 |
-
id="dataset-${key}" data-dataset="${key}" ${isSelected ? 'checked' : ''}>
|
| 156 |
-
<label class="form-check-label fw-bold" for="dataset-${key}">
|
| 157 |
-
${dataset.name}
|
| 158 |
-
</label>
|
| 159 |
-
</div>
|
| 160 |
-
<span class="badge bg-primary">${dataset.quality_score}/10</span>
|
| 161 |
-
</div>
|
| 162 |
-
|
| 163 |
-
<p class="text-muted mb-3">${dataset.description}</p>
|
| 164 |
-
|
| 165 |
-
<div class="mb-3">
|
| 166 |
-
<div class="row">
|
| 167 |
-
<div class="col-6">
|
| 168 |
-
<div class="size-indicator">
|
| 169 |
-
<i class="fas fa-hdd me-1"></i>
|
| 170 |
-
${dataset.size_gb} GB
|
| 171 |
-
</div>
|
| 172 |
-
</div>
|
| 173 |
-
<div class="col-6">
|
| 174 |
-
<div class="samples-indicator">
|
| 175 |
-
<i class="fas fa-images me-1"></i>
|
| 176 |
-
${dataset.num_samples.toLocaleString()} عينة
|
| 177 |
-
</div>
|
| 178 |
-
</div>
|
| 179 |
-
</div>
|
| 180 |
-
</div>
|
| 181 |
-
|
| 182 |
-
<div class="mb-3">
|
| 183 |
-
<small class="text-muted d-block mb-1">الوسائط:</small>
|
| 184 |
-
${dataset.modalities.map(mod =>
|
| 185 |
-
`<span class="modality-badge badge bg-secondary">${mod}</span>`
|
| 186 |
-
).join('')}
|
| 187 |
-
</div>
|
| 188 |
-
|
| 189 |
-
<div class="mb-3">
|
| 190 |
-
<small class="text-muted d-block mb-1">التخصصات:</small>
|
| 191 |
-
${dataset.medical_specialties.map(spec =>
|
| 192 |
-
`<span class="specialty-badge">${this.specialties[spec]?.name || spec}</span>`
|
| 193 |
-
).join('')}
|
| 194 |
-
</div>
|
| 195 |
-
|
| 196 |
-
<div class="mb-3">
|
| 197 |
-
<small class="text-muted d-block mb-1">اللغات:</small>
|
| 198 |
-
${dataset.languages.map(lang =>
|
| 199 |
-
`<span class="badge bg-light text-dark">${lang}</span>`
|
| 200 |
-
).join('')}
|
| 201 |
-
</div>
|
| 202 |
-
|
| 203 |
-
<div class="d-flex justify-content-between align-items-center">
|
| 204 |
-
<small class="text-muted">
|
| 205 |
-
<i class="fas fa-calendar me-1"></i>
|
| 206 |
-
آخر تحديث: ${dataset.last_updated}
|
| 207 |
-
</small>
|
| 208 |
-
<button class="btn btn-sm btn-outline-info" onclick="medicalDatasets.showDatasetDetails('${key}')">
|
| 209 |
-
<i class="fas fa-info-circle me-1"></i>تفاصيل
|
| 210 |
-
</button>
|
| 211 |
-
</div>
|
| 212 |
-
</div>
|
| 213 |
-
</div>
|
| 214 |
-
`;
|
| 215 |
-
});
|
| 216 |
-
|
| 217 |
-
grid.innerHTML = html;
|
| 218 |
-
this.updateSelectionSummary();
|
| 219 |
-
|
| 220 |
-
// Re-setup event listeners after rendering
|
| 221 |
-
this.setupEventListeners();
|
| 222 |
-
}
|
| 223 |
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
let html = '<div class="form-check form-check-inline">';
|
| 229 |
-
html += '<input class="form-check-input specialty-filter" type="checkbox" id="specialty-all" checked>';
|
| 230 |
-
html += '<label class="form-check-label" for="specialty-all">جميع التخصصات</label>';
|
| 231 |
-
html += '</div>';
|
| 232 |
-
|
| 233 |
-
Object.entries(this.specialties).forEach(([key, specialty]) => {
|
| 234 |
-
html += `
|
| 235 |
-
<div class="form-check form-check-inline">
|
| 236 |
-
<input class="form-check-input specialty-filter" type="checkbox"
|
| 237 |
-
id="specialty-${key}" data-specialty="${key}">
|
| 238 |
-
<label class="form-check-label" for="specialty-${key}">
|
| 239 |
-
${specialty.name}
|
| 240 |
-
</label>
|
| 241 |
-
</div>
|
| 242 |
-
`;
|
| 243 |
-
});
|
| 244 |
-
|
| 245 |
-
container.innerHTML = html;
|
| 246 |
-
}
|
| 247 |
-
|
| 248 |
-
handleDatasetSelection(checkbox) {
|
| 249 |
-
const datasetKey = checkbox.dataset.dataset;
|
| 250 |
-
|
| 251 |
-
if (checkbox.checked) {
|
| 252 |
-
this.selectedDatasets.add(datasetKey);
|
| 253 |
-
checkbox.closest('.dataset-card').classList.add('border-success');
|
| 254 |
-
} else {
|
| 255 |
-
this.selectedDatasets.delete(datasetKey);
|
| 256 |
-
checkbox.closest('.dataset-card').classList.remove('border-success');
|
| 257 |
-
}
|
| 258 |
-
|
| 259 |
-
this.updateSelectionSummary();
|
| 260 |
-
}
|
| 261 |
-
|
| 262 |
-
handleSpecialtyFilter() {
|
| 263 |
-
const allFilter = document.getElementById('specialty-all');
|
| 264 |
-
const specialtyFilters = document.querySelectorAll('.specialty-filter:not(#specialty-all)');
|
| 265 |
-
const datasetItems = document.querySelectorAll('.dataset-item');
|
| 266 |
-
|
| 267 |
-
if (allFilter.checked) {
|
| 268 |
-
// Show all datasets
|
| 269 |
-
datasetItems.forEach(item => item.style.display = 'block');
|
| 270 |
-
specialtyFilters.forEach(filter => filter.checked = false);
|
| 271 |
-
} else {
|
| 272 |
-
// Filter by selected specialties
|
| 273 |
-
const selectedSpecialties = Array.from(specialtyFilters)
|
| 274 |
-
.filter(filter => filter.checked)
|
| 275 |
-
.map(filter => filter.dataset.specialty);
|
| 276 |
-
|
| 277 |
-
datasetItems.forEach(item => {
|
| 278 |
-
const itemSpecialties = item.dataset.specialties.split(',');
|
| 279 |
-
const hasMatchingSpecialty = selectedSpecialties.some(spec =>
|
| 280 |
-
itemSpecialties.includes(spec)
|
| 281 |
-
);
|
| 282 |
-
|
| 283 |
-
item.style.display = hasMatchingSpecialty || selectedSpecialties.length === 0 ? 'block' : 'none';
|
| 284 |
-
});
|
| 285 |
-
}
|
| 286 |
-
}
|
| 287 |
-
|
| 288 |
-
updateSelectionSummary() {
|
| 289 |
-
const summary = document.getElementById('selected-summary');
|
| 290 |
-
if (!summary) return;
|
| 291 |
-
|
| 292 |
-
if (this.selectedDatasets.size === 0) {
|
| 293 |
-
summary.innerHTML = 'لم يتم اختيار أي قواعد بيانات بعد';
|
| 294 |
-
summary.className = 'alert alert-info';
|
| 295 |
-
} else {
|
| 296 |
-
const selectedList = Array.from(this.selectedDatasets);
|
| 297 |
-
const totalSize = selectedList.reduce((sum, key) => {
|
| 298 |
-
return sum + (this.datasets[key]?.size_gb || 0);
|
| 299 |
-
}, 0);
|
| 300 |
-
|
| 301 |
-
const totalSamples = selectedList.reduce((sum, key) => {
|
| 302 |
-
return sum + (this.datasets[key]?.num_samples || 0);
|
| 303 |
-
}, 0);
|
| 304 |
-
|
| 305 |
-
summary.innerHTML = `
|
| 306 |
-
<strong>تم اختيار ${this.selectedDatasets.size} قواعد بيانات</strong><br>
|
| 307 |
-
<small>الحجم الإجمالي: ${totalSize.toFixed(1)} GB | العينات: ${totalSamples.toLocaleString()}</small>
|
| 308 |
-
`;
|
| 309 |
-
summary.className = 'alert alert-success';
|
| 310 |
-
}
|
| 311 |
-
}
|
| 312 |
-
|
| 313 |
-
async saveSelections() {
|
| 314 |
-
try {
|
| 315 |
-
this.showLoading('جاري حفظ الاختيارات...');
|
| 316 |
-
|
| 317 |
-
const formData = new FormData();
|
| 318 |
-
formData.append('user_session', this.userSession);
|
| 319 |
-
formData.append('selected_datasets', JSON.stringify(Array.from(this.selectedDatasets)));
|
| 320 |
-
formData.append('preferences', JSON.stringify(this.userPreferences));
|
| 321 |
-
|
| 322 |
-
const response = await fetch('/api/medical-datasets/select', {
|
| 323 |
-
method: 'POST',
|
| 324 |
-
body: formData
|
| 325 |
-
});
|
| 326 |
-
|
| 327 |
-
const data = await response.json();
|
| 328 |
-
|
| 329 |
-
if (response.ok && data.success) {
|
| 330 |
-
this.showSuccess(data.message);
|
| 331 |
-
|
| 332 |
-
// Show validation warnings if any
|
| 333 |
-
if (data.validation_result.warnings.length > 0) {
|
| 334 |
-
data.validation_result.warnings.forEach(warning => {
|
| 335 |
-
this.showWarning(warning);
|
| 336 |
-
});
|
| 337 |
-
}
|
| 338 |
-
} else {
|
| 339 |
-
this.showError('فشل في حفظ الاختيارات: ' + (data.detail || 'خطأ غير معروف'));
|
| 340 |
}
|
| 341 |
} catch (error) {
|
| 342 |
-
console.error('Error
|
| 343 |
-
this.showError('خطأ في الاتصال بالخادم');
|
| 344 |
-
} finally {
|
| 345 |
-
this.hideLoading();
|
| 346 |
}
|
| 347 |
}
|
| 348 |
|
| 349 |
-
async clearSelections() {
|
| 350 |
-
if (!confirm('هل أنت متأكد من مسح جميع الاختيارات؟')) {
|
| 351 |
-
return;
|
| 352 |
-
}
|
| 353 |
-
|
| 354 |
-
this.selectedDatasets.clear();
|
| 355 |
-
|
| 356 |
-
// Update UI
|
| 357 |
-
document.querySelectorAll('.dataset-checkbox').forEach(checkbox => {
|
| 358 |
-
checkbox.checked = false;
|
| 359 |
-
checkbox.closest('.dataset-card').classList.remove('border-success');
|
| 360 |
-
});
|
| 361 |
-
|
| 362 |
-
this.updateSelectionSummary();
|
| 363 |
-
|
| 364 |
-
// Save empty selections
|
| 365 |
-
await this.saveSelections();
|
| 366 |
-
}
|
| 367 |
-
|
| 368 |
updateSystemInfo() {
|
| 369 |
const memoryElement = document.getElementById('memory-usage');
|
| 370 |
const cpuElement = document.getElementById('cpu-cores');
|
|
@@ -684,98 +377,6 @@ class MedicalDatasetsManager {
|
|
| 684 |
const toast = new bootstrap.Toast(document.getElementById('error-toast'));
|
| 685 |
toast.show();
|
| 686 |
}
|
| 687 |
-
|
| 688 |
-
async autoSaveSelections() {
|
| 689 |
-
if (this.selectedDatasets.size > 0) {
|
| 690 |
-
try {
|
| 691 |
-
await this.saveSelections();
|
| 692 |
-
} catch (error) {
|
| 693 |
-
console.error('Auto-save failed:', error);
|
| 694 |
-
}
|
| 695 |
-
}
|
| 696 |
-
}
|
| 697 |
-
|
| 698 |
-
showDatasetDetails(datasetKey) {
|
| 699 |
-
const dataset = this.datasets[datasetKey];
|
| 700 |
-
if (!dataset) return;
|
| 701 |
-
|
| 702 |
-
const modal = new bootstrap.Modal(document.getElementById('datasetDetailsModal'));
|
| 703 |
-
const title = document.getElementById('dataset-details-title');
|
| 704 |
-
const content = document.getElementById('dataset-details-content');
|
| 705 |
-
|
| 706 |
-
title.innerHTML = `<i class="fas fa-info-circle me-2"></i>${dataset.name}`;
|
| 707 |
-
|
| 708 |
-
content.innerHTML = `
|
| 709 |
-
<div class="row">
|
| 710 |
-
<div class="col-md-6">
|
| 711 |
-
<h6>معلومات أساسية</h6>
|
| 712 |
-
<table class="table table-sm">
|
| 713 |
-
<tr><td><strong>الحجم:</strong></td><td>${dataset.size_gb} GB</td></tr>
|
| 714 |
-
<tr><td><strong>العينات:</strong></td><td>${dataset.num_samples.toLocaleString()}</td></tr>
|
| 715 |
-
<tr><td><strong>جودة البيانات:</strong></td><td>${dataset.quality_score}/10</td></tr>
|
| 716 |
-
<tr><td><strong>مستوى الصعوبة:</strong></td><td>${dataset.difficulty_level}</td></tr>
|
| 717 |
-
<tr><td><strong>الترخيص:</strong></td><td>${dataset.license}</td></tr>
|
| 718 |
-
</table>
|
| 719 |
-
</div>
|
| 720 |
-
<div class="col-md-6">
|
| 721 |
-
<h6>التفاصيل التقنية</h6>
|
| 722 |
-
<p><strong>الوسائط:</strong><br>
|
| 723 |
-
${dataset.modalities.map(mod => `<span class="badge bg-secondary me-1">${mod}</span>`).join('')}</p>
|
| 724 |
-
|
| 725 |
-
<p><strong>التخصصات الطبية:</strong><br>
|
| 726 |
-
${dataset.medical_specialties.map(spec =>
|
| 727 |
-
`<span class="badge bg-info me-1">${this.specialties[spec]?.name || spec}</span>`
|
| 728 |
-
).join('')}</p>
|
| 729 |
-
|
| 730 |
-
<p><strong>اللغات المدعومة:</strong><br>
|
| 731 |
-
${dataset.languages.map(lang => `<span class="badge bg-light text-dark me-1">${lang}</span>`).join('')}</p>
|
| 732 |
-
</div>
|
| 733 |
-
</div>
|
| 734 |
-
|
| 735 |
-
<div class="mt-3">
|
| 736 |
-
<h6>الوصف التفصيلي</h6>
|
| 737 |
-
<p>${dataset.description}</p>
|
| 738 |
-
</div>
|
| 739 |
-
|
| 740 |
-
<div class="mt-3">
|
| 741 |
-
<h6>مناسب لـ</h6>
|
| 742 |
-
<ul>
|
| 743 |
-
${dataset.recommended_for.map(item => `<li>${item}</li>`).join('')}
|
| 744 |
-
</ul>
|
| 745 |
-
</div>
|
| 746 |
-
`;
|
| 747 |
-
|
| 748 |
-
modal.show();
|
| 749 |
-
}
|
| 750 |
-
|
| 751 |
-
showWarning(message) {
|
| 752 |
-
// Create a warning toast if it doesn't exist
|
| 753 |
-
let warningToast = document.getElementById('warning-toast');
|
| 754 |
-
if (!warningToast) {
|
| 755 |
-
const container = document.querySelector('.toast-container');
|
| 756 |
-
warningToast = document.createElement('div');
|
| 757 |
-
warningToast.id = 'warning-toast';
|
| 758 |
-
warningToast.className = 'toast';
|
| 759 |
-
warningToast.innerHTML = `
|
| 760 |
-
<div class="toast-header bg-warning text-dark">
|
| 761 |
-
<i class="fas fa-exclamation-triangle me-2"></i>
|
| 762 |
-
<strong class="me-auto">تحذير</strong>
|
| 763 |
-
<button type="button" class="btn-close" data-bs-dismiss="toast"></button>
|
| 764 |
-
</div>
|
| 765 |
-
<div class="toast-body" id="warning-message"></div>
|
| 766 |
-
`;
|
| 767 |
-
container.appendChild(warningToast);
|
| 768 |
-
}
|
| 769 |
-
|
| 770 |
-
const messageEl = document.getElementById('warning-message');
|
| 771 |
-
messageEl.textContent = message;
|
| 772 |
-
const bsToast = new bootstrap.Toast(warningToast);
|
| 773 |
-
bsToast.show();
|
| 774 |
-
}
|
| 775 |
-
|
| 776 |
-
refreshDatasets() {
|
| 777 |
-
this.loadDatasets();
|
| 778 |
-
}
|
| 779 |
}
|
| 780 |
|
| 781 |
// Initialize medical datasets manager when page loads
|
|
|
|
| 1 |
/**
|
| 2 |
* Medical Datasets Manager JavaScript
|
| 3 |
+
* Handles medical datasets functionality
|
| 4 |
*/
|
| 5 |
|
| 6 |
class MedicalDatasetsManager {
|
| 7 |
constructor() {
|
| 8 |
+
this.datasets = [];
|
| 9 |
+
this.loadedDatasets = new Set();
|
| 10 |
+
this.systemInfo = {};
|
|
|
|
|
|
|
| 11 |
this.init();
|
| 12 |
}
|
| 13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
init() {
|
| 15 |
this.loadDatasets();
|
| 16 |
+
this.loadSystemInfo();
|
| 17 |
this.setupEventListeners();
|
| 18 |
+
|
| 19 |
+
// Refresh system info every 30 seconds
|
| 20 |
+
setInterval(() => this.loadSystemInfo(), 30000);
|
| 21 |
}
|
| 22 |
|
| 23 |
setupEventListeners() {
|
| 24 |
+
// Dataset loading modal events
|
| 25 |
+
document.getElementById('load-dataset-btn').addEventListener('click', () => {
|
| 26 |
+
this.loadSelectedDataset();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 28 |
}
|
| 29 |
|
| 30 |
async loadDatasets() {
|
| 31 |
try {
|
|
|
|
|
|
|
| 32 |
const response = await fetch('/api/medical-datasets');
|
| 33 |
const data = await response.json();
|
| 34 |
+
|
| 35 |
+
if (response.ok) {
|
| 36 |
this.datasets = data.datasets;
|
|
|
|
| 37 |
this.renderDatasets();
|
|
|
|
| 38 |
} else {
|
| 39 |
+
this.showError('فشل في تحميل قواعد البيانات');
|
| 40 |
}
|
| 41 |
} catch (error) {
|
| 42 |
console.error('Error loading datasets:', error);
|
| 43 |
this.showError('خطأ في الاتصال بالخادم');
|
|
|
|
|
|
|
| 44 |
}
|
| 45 |
}
|
| 46 |
|
| 47 |
+
async loadSystemInfo() {
|
| 48 |
try {
|
| 49 |
+
const response = await fetch('/api/system/performance');
|
| 50 |
const data = await response.json();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 51 |
|
| 52 |
+
if (response.ok) {
|
| 53 |
+
this.systemInfo = data;
|
| 54 |
+
this.updateSystemInfo();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
}
|
| 56 |
} catch (error) {
|
| 57 |
+
console.error('Error loading system info:', error);
|
|
|
|
|
|
|
|
|
|
| 58 |
}
|
| 59 |
}
|
| 60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
updateSystemInfo() {
|
| 62 |
const memoryElement = document.getElementById('memory-usage');
|
| 63 |
const cpuElement = document.getElementById('cpu-cores');
|
|
|
|
| 377 |
const toast = new bootstrap.Toast(document.getElementById('error-toast'));
|
| 378 |
toast.show();
|
| 379 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 380 |
}
|
| 381 |
|
| 382 |
// Initialize medical datasets manager when page loads
|
static/js/model-manager.js
DELETED
|
@@ -1,504 +0,0 @@
|
|
| 1 |
-
/**
|
| 2 |
-
* Model Manager JavaScript
|
| 3 |
-
* Handles Google models and teacher/student model selection
|
| 4 |
-
*/
|
| 5 |
-
|
| 6 |
-
class ModelManager {
|
| 7 |
-
constructor() {
|
| 8 |
-
this.availableModels = {};
|
| 9 |
-
this.selectedTeachers = [];
|
| 10 |
-
this.selectedStudent = null;
|
| 11 |
-
this.userSession = this.generateUserSession();
|
| 12 |
-
this.init();
|
| 13 |
-
}
|
| 14 |
-
|
| 15 |
-
generateUserSession() {
|
| 16 |
-
let sessionId = localStorage.getItem('model_user_session');
|
| 17 |
-
if (!sessionId) {
|
| 18 |
-
sessionId = 'model_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
|
| 19 |
-
localStorage.setItem('model_user_session', sessionId);
|
| 20 |
-
}
|
| 21 |
-
return sessionId;
|
| 22 |
-
}
|
| 23 |
-
|
| 24 |
-
init() {
|
| 25 |
-
this.loadAvailableModels();
|
| 26 |
-
this.loadUserConfiguration();
|
| 27 |
-
this.setupEventListeners();
|
| 28 |
-
|
| 29 |
-
// Auto-save configuration every 30 seconds
|
| 30 |
-
setInterval(() => this.autoSaveConfiguration(), 30000);
|
| 31 |
-
}
|
| 32 |
-
|
| 33 |
-
setupEventListeners() {
|
| 34 |
-
// Filter controls
|
| 35 |
-
document.getElementById('model-type-filter').addEventListener('change', () => this.applyFilters());
|
| 36 |
-
document.getElementById('model-size-filter').addEventListener('change', () => this.applyFilters());
|
| 37 |
-
document.getElementById('model-search').addEventListener('input', () => this.applyFilters());
|
| 38 |
-
|
| 39 |
-
// Student model option toggle
|
| 40 |
-
document.querySelectorAll('input[name="student-option"]').forEach(radio => {
|
| 41 |
-
radio.addEventListener('change', () => this.toggleStudentOptions());
|
| 42 |
-
});
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
async loadAvailableModels() {
|
| 46 |
-
try {
|
| 47 |
-
this.showLoading();
|
| 48 |
-
|
| 49 |
-
const response = await fetch('/api/google-models');
|
| 50 |
-
const data = await response.json();
|
| 51 |
-
|
| 52 |
-
if (response.ok && data.success) {
|
| 53 |
-
this.availableModels = data.models;
|
| 54 |
-
this.renderModels();
|
| 55 |
-
} else {
|
| 56 |
-
this.showError('فشل في تحميل النماذج: ' + (data.detail || 'خطأ غير معروف'));
|
| 57 |
-
}
|
| 58 |
-
} catch (error) {
|
| 59 |
-
console.error('Error loading models:', error);
|
| 60 |
-
this.showError('خطأ في الاتصال بالخادم');
|
| 61 |
-
} finally {
|
| 62 |
-
this.hideLoading();
|
| 63 |
-
}
|
| 64 |
-
}
|
| 65 |
-
|
| 66 |
-
async loadUserConfiguration() {
|
| 67 |
-
try {
|
| 68 |
-
const response = await fetch(`/api/model-configuration/${this.userSession}`);
|
| 69 |
-
const data = await response.json();
|
| 70 |
-
|
| 71 |
-
if (response.ok && data.success) {
|
| 72 |
-
this.selectedTeachers = data.teachers || [];
|
| 73 |
-
this.selectedStudent = data.student || null;
|
| 74 |
-
this.updateSelectionUI();
|
| 75 |
-
}
|
| 76 |
-
} catch (error) {
|
| 77 |
-
console.error('Error loading user configuration:', error);
|
| 78 |
-
}
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
renderModels() {
|
| 82 |
-
const grid = document.getElementById('models-grid');
|
| 83 |
-
if (!grid) return;
|
| 84 |
-
|
| 85 |
-
if (Object.keys(this.availableModels).length === 0) {
|
| 86 |
-
grid.innerHTML = `
|
| 87 |
-
<div class="col-12 text-center">
|
| 88 |
-
<div class="alert alert-info">
|
| 89 |
-
<i class="fas fa-info-circle me-2"></i>
|
| 90 |
-
لا توجد نماذج متاحة حالياً
|
| 91 |
-
</div>
|
| 92 |
-
</div>
|
| 93 |
-
`;
|
| 94 |
-
return;
|
| 95 |
-
}
|
| 96 |
-
|
| 97 |
-
let html = '';
|
| 98 |
-
Object.entries(this.availableModels).forEach(([key, model]) => {
|
| 99 |
-
const isSelected = this.selectedTeachers.some(t => t.model_key === key);
|
| 100 |
-
|
| 101 |
-
html += `
|
| 102 |
-
<div class="col-lg-6 mb-4 model-item"
|
| 103 |
-
data-type="${model.type}"
|
| 104 |
-
data-size="${model.size_category}"
|
| 105 |
-
data-name="${model.name.toLowerCase()}">
|
| 106 |
-
<div class="model-card ${isSelected ? 'selected' : ''}" data-model-key="${key}">
|
| 107 |
-
<div class="model-status ${isSelected ? 'status-selected' : 'status-available'}">
|
| 108 |
-
${isSelected ? 'مختار' : 'متاح'}
|
| 109 |
-
</div>
|
| 110 |
-
|
| 111 |
-
<div class="d-flex justify-content-between align-items-start mb-3">
|
| 112 |
-
<div>
|
| 113 |
-
<h6 class="mb-1">${model.name}</h6>
|
| 114 |
-
<small class="text-muted">${model.description}</small>
|
| 115 |
-
</div>
|
| 116 |
-
<div class="parameter-count">
|
| 117 |
-
<i class="fas fa-microchip me-1"></i>
|
| 118 |
-
${model.parameters}
|
| 119 |
-
</div>
|
| 120 |
-
</div>
|
| 121 |
-
|
| 122 |
-
<div class="mb-3">
|
| 123 |
-
<div class="row">
|
| 124 |
-
<div class="col-6">
|
| 125 |
-
<small class="text-muted">النوع:</small><br>
|
| 126 |
-
<span class="model-type-badge badge bg-primary">${this.getTypeLabel(model.type)}</span>
|
| 127 |
-
</div>
|
| 128 |
-
<div class="col-6">
|
| 129 |
-
<small class="text-muted">الحجم:</small><br>
|
| 130 |
-
<span class="model-type-badge badge bg-secondary">${this.getSizeLabel(model.size_category)}</span>
|
| 131 |
-
</div>
|
| 132 |
-
</div>
|
| 133 |
-
</div>
|
| 134 |
-
|
| 135 |
-
<div class="mb-3">
|
| 136 |
-
<small class="text-muted">الوسائط المدعومة:</small><br>
|
| 137 |
-
${model.modalities.map(mod =>
|
| 138 |
-
`<span class="model-type-badge badge bg-info">${this.getModalityLabel(mod)}</span>`
|
| 139 |
-
).join('')}
|
| 140 |
-
</div>
|
| 141 |
-
|
| 142 |
-
<div class="mb-3">
|
| 143 |
-
<small class="text-muted">الاستخدامات:</small><br>
|
| 144 |
-
<small>${model.use_cases.join('، ')}</small>
|
| 145 |
-
</div>
|
| 146 |
-
|
| 147 |
-
<div class="d-flex justify-content-between align-items-center">
|
| 148 |
-
<div>
|
| 149 |
-
<small class="text-muted">
|
| 150 |
-
<i class="fas fa-star me-1"></i>
|
| 151 |
-
تقييم: ${model.performance_score}/10
|
| 152 |
-
</small>
|
| 153 |
-
</div>
|
| 154 |
-
<button class="btn btn-sm ${isSelected ? 'btn-danger' : 'btn-primary'}"
|
| 155 |
-
onclick="modelManager.${isSelected ? 'removeTeacher' : 'addToTeachers'}('${key}')">
|
| 156 |
-
<i class="fas ${isSelected ? 'fa-minus' : 'fa-plus'} me-1"></i>
|
| 157 |
-
${isSelected ? 'إزالة' : 'إضافة للمعلمين'}
|
| 158 |
-
</button>
|
| 159 |
-
</div>
|
| 160 |
-
</div>
|
| 161 |
-
</div>
|
| 162 |
-
`;
|
| 163 |
-
});
|
| 164 |
-
|
| 165 |
-
grid.innerHTML = html;
|
| 166 |
-
}
|
| 167 |
-
|
| 168 |
-
getTypeLabel(type) {
|
| 169 |
-
const labels = {
|
| 170 |
-
'text': 'نصوص',
|
| 171 |
-
'vision': 'رؤية',
|
| 172 |
-
'multimodal': 'متعدد الوسائط',
|
| 173 |
-
'audio': 'صوت'
|
| 174 |
-
};
|
| 175 |
-
return labels[type] || type;
|
| 176 |
-
}
|
| 177 |
-
|
| 178 |
-
getSizeLabel(size) {
|
| 179 |
-
const labels = {
|
| 180 |
-
'small': 'صغير',
|
| 181 |
-
'medium': 'متوسط',
|
| 182 |
-
'large': 'كبير',
|
| 183 |
-
'xlarge': 'كبير جداً'
|
| 184 |
-
};
|
| 185 |
-
return labels[size] || size;
|
| 186 |
-
}
|
| 187 |
-
|
| 188 |
-
getModalityLabel(modality) {
|
| 189 |
-
const labels = {
|
| 190 |
-
'text': 'نص',
|
| 191 |
-
'vision': 'صورة',
|
| 192 |
-
'audio': 'صوت',
|
| 193 |
-
'video': 'فيديو'
|
| 194 |
-
};
|
| 195 |
-
return labels[modality] || modality;
|
| 196 |
-
}
|
| 197 |
-
|
| 198 |
-
applyFilters() {
|
| 199 |
-
const typeFilter = document.getElementById('model-type-filter').value;
|
| 200 |
-
const sizeFilter = document.getElementById('model-size-filter').value;
|
| 201 |
-
const searchTerm = document.getElementById('model-search').value.toLowerCase();
|
| 202 |
-
|
| 203 |
-
document.querySelectorAll('.model-item').forEach(item => {
|
| 204 |
-
const type = item.dataset.type;
|
| 205 |
-
const size = item.dataset.size;
|
| 206 |
-
const name = item.dataset.name;
|
| 207 |
-
|
| 208 |
-
const typeMatch = !typeFilter || type === typeFilter;
|
| 209 |
-
const sizeMatch = !sizeFilter || size === sizeFilter;
|
| 210 |
-
const searchMatch = !searchTerm || name.includes(searchTerm);
|
| 211 |
-
|
| 212 |
-
item.style.display = typeMatch && sizeMatch && searchMatch ? 'block' : 'none';
|
| 213 |
-
});
|
| 214 |
-
}
|
| 215 |
-
|
| 216 |
-
addToTeachers(modelKey) {
|
| 217 |
-
const model = this.availableModels[modelKey];
|
| 218 |
-
if (!model) return;
|
| 219 |
-
|
| 220 |
-
// Check if already selected
|
| 221 |
-
if (this.selectedTeachers.some(t => t.model_key === modelKey)) {
|
| 222 |
-
this.showError('هذا النموذج مختار بالفعل');
|
| 223 |
-
return;
|
| 224 |
-
}
|
| 225 |
-
|
| 226 |
-
// Add to selected teachers
|
| 227 |
-
this.selectedTeachers.push({
|
| 228 |
-
model_key: modelKey,
|
| 229 |
-
name: model.name,
|
| 230 |
-
type: model.type,
|
| 231 |
-
modalities: model.modalities,
|
| 232 |
-
source: 'google'
|
| 233 |
-
});
|
| 234 |
-
|
| 235 |
-
this.updateSelectionUI();
|
| 236 |
-
this.renderModels(); // Re-render to update selection status
|
| 237 |
-
this.showSuccess(`تم إضافة ${model.name} للنماذج المعلمة`);
|
| 238 |
-
}
|
| 239 |
-
|
| 240 |
-
removeTeacher(modelKey) {
|
| 241 |
-
this.selectedTeachers = this.selectedTeachers.filter(t => t.model_key !== modelKey);
|
| 242 |
-
this.updateSelectionUI();
|
| 243 |
-
this.renderModels(); // Re-render to update selection status
|
| 244 |
-
this.showSuccess('تم إزالة النموذج من المعلمين');
|
| 245 |
-
}
|
| 246 |
-
|
| 247 |
-
updateSelectionUI() {
|
| 248 |
-
this.updateTeachersDisplay();
|
| 249 |
-
this.updateStudentDisplay();
|
| 250 |
-
this.updateSaveButton();
|
| 251 |
-
}
|
| 252 |
-
|
| 253 |
-
updateTeachersDisplay() {
|
| 254 |
-
const container = document.getElementById('selected-teachers');
|
| 255 |
-
if (!container) return;
|
| 256 |
-
|
| 257 |
-
if (this.selectedTeachers.length === 0) {
|
| 258 |
-
container.innerHTML = '<p class="text-muted">لم يتم اختيار نماذج معلمة بعد</p>';
|
| 259 |
-
} else {
|
| 260 |
-
let html = '';
|
| 261 |
-
this.selectedTeachers.forEach((teacher, index) => {
|
| 262 |
-
html += `
|
| 263 |
-
<div class="d-flex justify-content-between align-items-center mb-2 p-2 bg-light rounded">
|
| 264 |
-
<div>
|
| 265 |
-
<small class="fw-bold">${teacher.name}</small><br>
|
| 266 |
-
<small class="text-muted">${this.getTypeLabel(teacher.type)}</small>
|
| 267 |
-
</div>
|
| 268 |
-
<button class="btn btn-sm btn-outline-danger" onclick="modelManager.removeTeacher('${teacher.model_key}')">
|
| 269 |
-
<i class="fas fa-times"></i>
|
| 270 |
-
</button>
|
| 271 |
-
</div>
|
| 272 |
-
`;
|
| 273 |
-
});
|
| 274 |
-
container.innerHTML = html;
|
| 275 |
-
}
|
| 276 |
-
}
|
| 277 |
-
|
| 278 |
-
updateStudentDisplay() {
|
| 279 |
-
const container = document.getElementById('selected-student');
|
| 280 |
-
if (!container) return;
|
| 281 |
-
|
| 282 |
-
if (!this.selectedStudent) {
|
| 283 |
-
container.innerHTML = '<p class="text-muted">سيتم إنشاء نموذج جديد</p>';
|
| 284 |
-
} else {
|
| 285 |
-
container.innerHTML = `
|
| 286 |
-
<div class="p-2 bg-light rounded">
|
| 287 |
-
<small class="fw-bold">${this.selectedStudent.name || 'نموذج مخصص'}</small><br>
|
| 288 |
-
<small class="text-muted">${this.selectedStudent.type || 'جديد'}</small>
|
| 289 |
-
</div>
|
| 290 |
-
`;
|
| 291 |
-
}
|
| 292 |
-
}
|
| 293 |
-
|
| 294 |
-
updateSaveButton() {
|
| 295 |
-
const saveBtn = document.getElementById('save-config-btn');
|
| 296 |
-
if (saveBtn) {
|
| 297 |
-
saveBtn.disabled = this.selectedTeachers.length === 0;
|
| 298 |
-
}
|
| 299 |
-
}
|
| 300 |
-
|
| 301 |
-
showLoading() {
|
| 302 |
-
const grid = document.getElementById('models-grid');
|
| 303 |
-
if (grid) {
|
| 304 |
-
grid.innerHTML = `
|
| 305 |
-
<div class="col-12 text-center">
|
| 306 |
-
<div class="spinner-border text-primary" role="status">
|
| 307 |
-
<span class="visually-hidden">جاري التحميل...</span>
|
| 308 |
-
</div>
|
| 309 |
-
<p class="mt-2 text-muted">جاري تحميل النماذج...</p>
|
| 310 |
-
</div>
|
| 311 |
-
`;
|
| 312 |
-
}
|
| 313 |
-
}
|
| 314 |
-
|
| 315 |
-
hideLoading() {
|
| 316 |
-
// Loading will be hidden when models are rendered
|
| 317 |
-
}
|
| 318 |
-
|
| 319 |
-
showSuccess(message) {
|
| 320 |
-
const toast = document.getElementById('success-toast');
|
| 321 |
-
const messageEl = document.getElementById('success-message');
|
| 322 |
-
|
| 323 |
-
messageEl.textContent = message;
|
| 324 |
-
const bsToast = new bootstrap.Toast(toast);
|
| 325 |
-
bsToast.show();
|
| 326 |
-
}
|
| 327 |
-
|
| 328 |
-
showError(message) {
|
| 329 |
-
const toast = document.getElementById('error-toast');
|
| 330 |
-
const messageEl = document.getElementById('error-message');
|
| 331 |
-
|
| 332 |
-
messageEl.textContent = message;
|
| 333 |
-
const bsToast = new bootstrap.Toast(toast);
|
| 334 |
-
bsToast.show();
|
| 335 |
-
}
|
| 336 |
-
|
| 337 |
-
showAddTeacherModal() {
|
| 338 |
-
const modal = new bootstrap.Modal(document.getElementById('addTeacherModal'));
|
| 339 |
-
modal.show();
|
| 340 |
-
}
|
| 341 |
-
|
| 342 |
-
async addTeacherModel() {
|
| 343 |
-
const source = document.getElementById('teacher-source').value;
|
| 344 |
-
const path = document.getElementById('teacher-path').value.trim();
|
| 345 |
-
const modality = document.getElementById('teacher-modality').value;
|
| 346 |
-
|
| 347 |
-
if (!path) {
|
| 348 |
-
this.showError('يرجى إدخال اسم أو رابط النموذج');
|
| 349 |
-
return;
|
| 350 |
-
}
|
| 351 |
-
|
| 352 |
-
// Create teacher model object
|
| 353 |
-
const teacherModel = {
|
| 354 |
-
model_key: `custom_${Date.now()}`,
|
| 355 |
-
name: path.split('/').pop() || path,
|
| 356 |
-
type: modality,
|
| 357 |
-
modalities: [modality],
|
| 358 |
-
source: source,
|
| 359 |
-
path: path
|
| 360 |
-
};
|
| 361 |
-
|
| 362 |
-
// Add to selected teachers
|
| 363 |
-
this.selectedTeachers.push(teacherModel);
|
| 364 |
-
this.updateSelectionUI();
|
| 365 |
-
|
| 366 |
-
// Close modal
|
| 367 |
-
const modal = bootstrap.Modal.getInstance(document.getElementById('addTeacherModal'));
|
| 368 |
-
modal.hide();
|
| 369 |
-
|
| 370 |
-
// Clear form
|
| 371 |
-
document.getElementById('teacher-path').value = '';
|
| 372 |
-
|
| 373 |
-
this.showSuccess(`تم إضافة النموذج ${teacherModel.name} بنجاح`);
|
| 374 |
-
}
|
| 375 |
-
|
| 376 |
-
showSelectStudentModal() {
|
| 377 |
-
const modal = new bootstrap.Modal(document.getElementById('selectStudentModal'));
|
| 378 |
-
modal.show();
|
| 379 |
-
}
|
| 380 |
-
|
| 381 |
-
toggleStudentOptions() {
|
| 382 |
-
const existingOption = document.getElementById('existing-student');
|
| 383 |
-
const optionsDiv = document.getElementById('existing-student-options');
|
| 384 |
-
|
| 385 |
-
if (existingOption.checked) {
|
| 386 |
-
optionsDiv.style.display = 'block';
|
| 387 |
-
} else {
|
| 388 |
-
optionsDiv.style.display = 'none';
|
| 389 |
-
}
|
| 390 |
-
}
|
| 391 |
-
|
| 392 |
-
selectStudentModel() {
|
| 393 |
-
const newOption = document.getElementById('new-student');
|
| 394 |
-
const existingPath = document.getElementById('existing-student-path').value.trim();
|
| 395 |
-
|
| 396 |
-
if (newOption.checked) {
|
| 397 |
-
// New student model
|
| 398 |
-
this.selectedStudent = {
|
| 399 |
-
type: 'new',
|
| 400 |
-
name: 'نموذج جديد',
|
| 401 |
-
path: null
|
| 402 |
-
};
|
| 403 |
-
} else {
|
| 404 |
-
// Existing student model
|
| 405 |
-
if (!existingPath) {
|
| 406 |
-
this.showError('يرجى إدخال مسار النموذج الموجود');
|
| 407 |
-
return;
|
| 408 |
-
}
|
| 409 |
-
|
| 410 |
-
this.selectedStudent = {
|
| 411 |
-
type: 'existing',
|
| 412 |
-
name: existingPath.split('/').pop() || existingPath,
|
| 413 |
-
path: existingPath
|
| 414 |
-
};
|
| 415 |
-
}
|
| 416 |
-
|
| 417 |
-
this.updateSelectionUI();
|
| 418 |
-
|
| 419 |
-
// Close modal
|
| 420 |
-
const modal = bootstrap.Modal.getInstance(document.getElementById('selectStudentModal'));
|
| 421 |
-
modal.hide();
|
| 422 |
-
|
| 423 |
-
this.showSuccess('تم تحديد النموذج الطلابي بنجاح');
|
| 424 |
-
}
|
| 425 |
-
|
| 426 |
-
async saveConfiguration() {
|
| 427 |
-
try {
|
| 428 |
-
if (this.selectedTeachers.length === 0) {
|
| 429 |
-
this.showError('يجب اختيار نموذج معلم واحد على الأقل');
|
| 430 |
-
return;
|
| 431 |
-
}
|
| 432 |
-
|
| 433 |
-
const configuration = {
|
| 434 |
-
user_session: this.userSession,
|
| 435 |
-
teachers: this.selectedTeachers,
|
| 436 |
-
student: this.selectedStudent,
|
| 437 |
-
timestamp: new Date().toISOString()
|
| 438 |
-
};
|
| 439 |
-
|
| 440 |
-
const response = await fetch('/api/model-configuration/save', {
|
| 441 |
-
method: 'POST',
|
| 442 |
-
headers: {
|
| 443 |
-
'Content-Type': 'application/json'
|
| 444 |
-
},
|
| 445 |
-
body: JSON.stringify(configuration)
|
| 446 |
-
});
|
| 447 |
-
|
| 448 |
-
const data = await response.json();
|
| 449 |
-
|
| 450 |
-
if (response.ok && data.success) {
|
| 451 |
-
this.showSuccess('تم حفظ تكوين النماذج بنجاح');
|
| 452 |
-
|
| 453 |
-
// Store in localStorage for quick access
|
| 454 |
-
localStorage.setItem('model_configuration', JSON.stringify(configuration));
|
| 455 |
-
} else {
|
| 456 |
-
this.showError('فشل في حفظ التكوين: ' + (data.detail || 'خطأ غير معروف'));
|
| 457 |
-
}
|
| 458 |
-
} catch (error) {
|
| 459 |
-
console.error('Error saving configuration:', error);
|
| 460 |
-
this.showError('خطأ في الاتصال بالخادم');
|
| 461 |
-
}
|
| 462 |
-
}
|
| 463 |
-
|
| 464 |
-
async autoSaveConfiguration() {
|
| 465 |
-
if (this.selectedTeachers.length > 0) {
|
| 466 |
-
try {
|
| 467 |
-
await this.saveConfiguration();
|
| 468 |
-
} catch (error) {
|
| 469 |
-
console.error('Auto-save failed:', error);
|
| 470 |
-
}
|
| 471 |
-
}
|
| 472 |
-
}
|
| 473 |
-
|
| 474 |
-
clearSelection() {
|
| 475 |
-
if (!confirm('هل أنت متأكد من مسح جميع الاختيارات؟')) {
|
| 476 |
-
return;
|
| 477 |
-
}
|
| 478 |
-
|
| 479 |
-
this.selectedTeachers = [];
|
| 480 |
-
this.selectedStudent = null;
|
| 481 |
-
|
| 482 |
-
this.updateSelectionUI();
|
| 483 |
-
this.renderModels(); // Re-render to update selection status
|
| 484 |
-
|
| 485 |
-
this.showSuccess('تم مسح جميع الاختيارات');
|
| 486 |
-
}
|
| 487 |
-
|
| 488 |
-
refreshModels() {
|
| 489 |
-
this.loadAvailableModels();
|
| 490 |
-
}
|
| 491 |
-
|
| 492 |
-
// Export configuration for use in main training page
|
| 493 |
-
getConfiguration() {
|
| 494 |
-
return {
|
| 495 |
-
teachers: this.selectedTeachers,
|
| 496 |
-
student: this.selectedStudent
|
| 497 |
-
};
|
| 498 |
-
}
|
| 499 |
-
}
|
| 500 |
-
|
| 501 |
-
// Initialize model manager when page loads
|
| 502 |
-
document.addEventListener('DOMContentLoaded', () => {
|
| 503 |
-
window.modelManager = new ModelManager();
|
| 504 |
-
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
templates/google-models.html
DELETED
|
@@ -1,293 +0,0 @@
|
|
| 1 |
-
<!DOCTYPE html>
|
| 2 |
-
<html lang="ar" dir="rtl">
|
| 3 |
-
<head>
|
| 4 |
-
<meta charset="UTF-8">
|
| 5 |
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
-
<title>نماذج Google - منصة تقطير المعرفة</title>
|
| 7 |
-
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
| 8 |
-
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
|
| 9 |
-
<link href="/static/css/style.css" rel="stylesheet">
|
| 10 |
-
<style>
|
| 11 |
-
.model-card {
|
| 12 |
-
border: 1px solid #dee2e6;
|
| 13 |
-
border-radius: 12px;
|
| 14 |
-
padding: 20px;
|
| 15 |
-
margin-bottom: 20px;
|
| 16 |
-
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
| 17 |
-
transition: all 0.3s ease;
|
| 18 |
-
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
|
| 19 |
-
}
|
| 20 |
-
.model-card:hover {
|
| 21 |
-
transform: translateY(-2px);
|
| 22 |
-
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
| 23 |
-
}
|
| 24 |
-
.model-card.selected {
|
| 25 |
-
border-color: #28a745;
|
| 26 |
-
background: linear-gradient(135deg, #d4edda 0%, #ffffff 100%);
|
| 27 |
-
}
|
| 28 |
-
.model-type-badge {
|
| 29 |
-
font-size: 0.75em;
|
| 30 |
-
padding: 4px 8px;
|
| 31 |
-
margin: 2px;
|
| 32 |
-
border-radius: 12px;
|
| 33 |
-
}
|
| 34 |
-
.parameter-count {
|
| 35 |
-
display: inline-flex;
|
| 36 |
-
align-items: center;
|
| 37 |
-
background: #e3f2fd;
|
| 38 |
-
color: #1976d2;
|
| 39 |
-
padding: 4px 8px;
|
| 40 |
-
border-radius: 6px;
|
| 41 |
-
font-size: 0.8em;
|
| 42 |
-
font-weight: 500;
|
| 43 |
-
}
|
| 44 |
-
.model-status {
|
| 45 |
-
position: absolute;
|
| 46 |
-
top: 10px;
|
| 47 |
-
right: 10px;
|
| 48 |
-
padding: 4px 8px;
|
| 49 |
-
border-radius: 12px;
|
| 50 |
-
font-size: 0.7em;
|
| 51 |
-
font-weight: bold;
|
| 52 |
-
}
|
| 53 |
-
.status-available { background: #d4edda; color: #155724; }
|
| 54 |
-
.status-selected { background: #cce5ff; color: #004085; }
|
| 55 |
-
.selection-summary {
|
| 56 |
-
position: sticky;
|
| 57 |
-
top: 20px;
|
| 58 |
-
background: white;
|
| 59 |
-
border: 1px solid #dee2e6;
|
| 60 |
-
border-radius: 12px;
|
| 61 |
-
padding: 20px;
|
| 62 |
-
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
| 63 |
-
}
|
| 64 |
-
</style>
|
| 65 |
-
</head>
|
| 66 |
-
<body>
|
| 67 |
-
<!-- Navigation -->
|
| 68 |
-
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
|
| 69 |
-
<div class="container">
|
| 70 |
-
<a class="navbar-brand" href="/">
|
| 71 |
-
<i class="fas fa-brain me-2"></i>
|
| 72 |
-
منصة تقطير المعرفة
|
| 73 |
-
</a>
|
| 74 |
-
<div class="navbar-nav ms-auto">
|
| 75 |
-
<a class="nav-link" href="/">الرئيسية</a>
|
| 76 |
-
<a class="nav-link" href="/tokens">إدارة الرموز</a>
|
| 77 |
-
<a class="nav-link" href="/medical-datasets">البيانات الطبية</a>
|
| 78 |
-
<a class="nav-link active" href="/google-models">نماذج Google</a>
|
| 79 |
-
</div>
|
| 80 |
-
</div>
|
| 81 |
-
</nav>
|
| 82 |
-
|
| 83 |
-
<div class="container mt-4">
|
| 84 |
-
<div class="row">
|
| 85 |
-
<!-- Models Grid -->
|
| 86 |
-
<div class="col-lg-8">
|
| 87 |
-
<div class="d-flex justify-content-between align-items-center mb-4">
|
| 88 |
-
<div>
|
| 89 |
-
<h2><i class="fas fa-robot me-2"></i>نماذج Google المتاحة</h2>
|
| 90 |
-
<p class="text-muted">اختر النماذج المعلمة لتدريب نموذجك الطلابي</p>
|
| 91 |
-
</div>
|
| 92 |
-
<div>
|
| 93 |
-
<button class="btn btn-outline-primary" onclick="modelManager.refreshModels()">
|
| 94 |
-
<i class="fas fa-sync-alt me-2"></i>تحديث
|
| 95 |
-
</button>
|
| 96 |
-
</div>
|
| 97 |
-
</div>
|
| 98 |
-
|
| 99 |
-
<!-- Filter Controls -->
|
| 100 |
-
<div class="card mb-4">
|
| 101 |
-
<div class="card-header">
|
| 102 |
-
<h6><i class="fas fa-filter me-2"></i>تصفية النماذج</h6>
|
| 103 |
-
</div>
|
| 104 |
-
<div class="card-body">
|
| 105 |
-
<div class="row">
|
| 106 |
-
<div class="col-md-4">
|
| 107 |
-
<label class="form-label">نوع النموذج:</label>
|
| 108 |
-
<select class="form-select" id="model-type-filter">
|
| 109 |
-
<option value="">جميع الأنواع</option>
|
| 110 |
-
<option value="text">نصوص</option>
|
| 111 |
-
<option value="vision">رؤية</option>
|
| 112 |
-
<option value="multimodal">متعدد الوسائط</option>
|
| 113 |
-
</select>
|
| 114 |
-
</div>
|
| 115 |
-
<div class="col-md-4">
|
| 116 |
-
<label class="form-label">حجم النموذج:</label>
|
| 117 |
-
<select class="form-select" id="model-size-filter">
|
| 118 |
-
<option value="">جميع الأحجام</option>
|
| 119 |
-
<option value="small">صغير (< 1B)</option>
|
| 120 |
-
<option value="medium">متوسط (1B - 10B)</option>
|
| 121 |
-
<option value="large">كبير (> 10B)</option>
|
| 122 |
-
</select>
|
| 123 |
-
</div>
|
| 124 |
-
<div class="col-md-4">
|
| 125 |
-
<label class="form-label">البحث:</label>
|
| 126 |
-
<input type="text" class="form-control" id="model-search" placeholder="ابحث عن نموذج...">
|
| 127 |
-
</div>
|
| 128 |
-
</div>
|
| 129 |
-
</div>
|
| 130 |
-
</div>
|
| 131 |
-
|
| 132 |
-
<!-- Models Grid -->
|
| 133 |
-
<div id="models-grid" class="row">
|
| 134 |
-
<div class="col-12 text-center">
|
| 135 |
-
<div class="spinner-border text-primary" role="status">
|
| 136 |
-
<span class="visually-hidden">جاري تحميل النماذج...</span>
|
| 137 |
-
</div>
|
| 138 |
-
<p class="mt-2 text-muted">جاري تحميل النماذج المتاحة...</p>
|
| 139 |
-
</div>
|
| 140 |
-
</div>
|
| 141 |
-
</div>
|
| 142 |
-
|
| 143 |
-
<!-- Selection Summary -->
|
| 144 |
-
<div class="col-lg-4">
|
| 145 |
-
<div class="selection-summary">
|
| 146 |
-
<h5><i class="fas fa-list-check me-2"></i>النماذج المختارة</h5>
|
| 147 |
-
|
| 148 |
-
<!-- Teacher Models -->
|
| 149 |
-
<div class="mb-4">
|
| 150 |
-
<h6 class="text-primary">النماذج المعلمة</h6>
|
| 151 |
-
<div id="selected-teachers" class="mb-3">
|
| 152 |
-
<p class="text-muted">لم يتم اختيار نماذج معلمة بعد</p>
|
| 153 |
-
</div>
|
| 154 |
-
<button class="btn btn-sm btn-outline-primary w-100" onclick="modelManager.showAddTeacherModal()">
|
| 155 |
-
<i class="fas fa-plus me-2"></i>إضافة نموذج معلم
|
| 156 |
-
</button>
|
| 157 |
-
</div>
|
| 158 |
-
|
| 159 |
-
<!-- Student Model -->
|
| 160 |
-
<div class="mb-4">
|
| 161 |
-
<h6 class="text-success">النموذج الطلابي</h6>
|
| 162 |
-
<div id="selected-student" class="mb-3">
|
| 163 |
-
<p class="text-muted">سيتم إنشاء نموذج جديد</p>
|
| 164 |
-
</div>
|
| 165 |
-
<button class="btn btn-sm btn-outline-success w-100" onclick="modelManager.showSelectStudentModal()">
|
| 166 |
-
<i class="fas fa-graduation-cap me-2"></i>اختيار نموذج طلابي
|
| 167 |
-
</button>
|
| 168 |
-
</div>
|
| 169 |
-
|
| 170 |
-
<!-- Action Buttons -->
|
| 171 |
-
<div class="d-grid gap-2">
|
| 172 |
-
<button class="btn btn-success" onclick="modelManager.saveConfiguration()" id="save-config-btn" disabled>
|
| 173 |
-
<i class="fas fa-save me-2"></i>حفظ التكوين
|
| 174 |
-
</button>
|
| 175 |
-
<button class="btn btn-outline-danger" onclick="modelManager.clearSelection()">
|
| 176 |
-
<i class="fas fa-trash me-2"></i>مسح الاختيارات
|
| 177 |
-
</button>
|
| 178 |
-
<a href="/" class="btn btn-primary">
|
| 179 |
-
<i class="fas fa-arrow-right me-2"></i>العودة للتدريب
|
| 180 |
-
</a>
|
| 181 |
-
</div>
|
| 182 |
-
</div>
|
| 183 |
-
</div>
|
| 184 |
-
</div>
|
| 185 |
-
</div>
|
| 186 |
-
|
| 187 |
-
<!-- Add Teacher Model Modal -->
|
| 188 |
-
<div class="modal fade" id="addTeacherModal" tabindex="-1">
|
| 189 |
-
<div class="modal-dialog modal-lg">
|
| 190 |
-
<div class="modal-content">
|
| 191 |
-
<div class="modal-header">
|
| 192 |
-
<h5 class="modal-title">
|
| 193 |
-
<i class="fas fa-plus me-2"></i>إضافة نموذج معلم
|
| 194 |
-
</h5>
|
| 195 |
-
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| 196 |
-
</div>
|
| 197 |
-
<div class="modal-body">
|
| 198 |
-
<div class="mb-3">
|
| 199 |
-
<label class="form-label">مصدر النموذج:</label>
|
| 200 |
-
<select class="form-select" id="teacher-source">
|
| 201 |
-
<option value="huggingface">Hugging Face Hub</option>
|
| 202 |
-
<option value="google">Google Models</option>
|
| 203 |
-
<option value="custom">رابط مخصص</option>
|
| 204 |
-
</select>
|
| 205 |
-
</div>
|
| 206 |
-
<div class="mb-3">
|
| 207 |
-
<label class="form-label">اسم/رابط النموذج:</label>
|
| 208 |
-
<input type="text" class="form-control" id="teacher-path" placeholder="مثال: google/flan-t5-base">
|
| 209 |
-
</div>
|
| 210 |
-
<div class="mb-3">
|
| 211 |
-
<label class="form-label">نوع الوسائط:</label>
|
| 212 |
-
<select class="form-select" id="teacher-modality">
|
| 213 |
-
<option value="text">نصوص</option>
|
| 214 |
-
<option value="vision">رؤية</option>
|
| 215 |
-
<option value="multimodal">متعدد الوسائط</option>
|
| 216 |
-
</select>
|
| 217 |
-
</div>
|
| 218 |
-
</div>
|
| 219 |
-
<div class="modal-footer">
|
| 220 |
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
|
| 221 |
-
<button type="button" class="btn btn-primary" onclick="modelManager.addTeacherModel()">
|
| 222 |
-
<i class="fas fa-plus me-2"></i>إضافة النموذج
|
| 223 |
-
</button>
|
| 224 |
-
</div>
|
| 225 |
-
</div>
|
| 226 |
-
</div>
|
| 227 |
-
</div>
|
| 228 |
-
|
| 229 |
-
<!-- Select Student Model Modal -->
|
| 230 |
-
<div class="modal fade" id="selectStudentModal" tabindex="-1">
|
| 231 |
-
<div class="modal-dialog">
|
| 232 |
-
<div class="modal-content">
|
| 233 |
-
<div class="modal-header">
|
| 234 |
-
<h5 class="modal-title">
|
| 235 |
-
<i class="fas fa-graduation-cap me-2"></i>اختيار النموذج الطلابي
|
| 236 |
-
</h5>
|
| 237 |
-
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
|
| 238 |
-
</div>
|
| 239 |
-
<div class="modal-body">
|
| 240 |
-
<div class="form-check mb-3">
|
| 241 |
-
<input class="form-check-input" type="radio" name="student-option" id="new-student" value="new" checked>
|
| 242 |
-
<label class="form-check-label" for="new-student">
|
| 243 |
-
<strong>إنشاء نموذج جديد</strong><br>
|
| 244 |
-
<small class="text-muted">سيتم إنشاء نموذج طلابي جديد من الصفر</small>
|
| 245 |
-
</label>
|
| 246 |
-
</div>
|
| 247 |
-
<div class="form-check mb-3">
|
| 248 |
-
<input class="form-check-input" type="radio" name="student-option" id="existing-student" value="existing">
|
| 249 |
-
<label class="form-check-label" for="existing-student">
|
| 250 |
-
<strong>استخدام نموذج موجود</strong><br>
|
| 251 |
-
<small class="text-muted">اختيار نموذج مدرب مسبقاً للتدريب الإضافي</small>
|
| 252 |
-
</label>
|
| 253 |
-
</div>
|
| 254 |
-
<div id="existing-student-options" style="display: none;">
|
| 255 |
-
<label class="form-label">مسار النموذج:</label>
|
| 256 |
-
<input type="text" class="form-control" id="existing-student-path" placeholder="مثال: username/my-model">
|
| 257 |
-
</div>
|
| 258 |
-
</div>
|
| 259 |
-
<div class="modal-footer">
|
| 260 |
-
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">إلغاء</button>
|
| 261 |
-
<button type="button" class="btn btn-success" onclick="modelManager.selectStudentModel()">
|
| 262 |
-
<i class="fas fa-check me-2"></i>تأكيد الاختيار
|
| 263 |
-
</button>
|
| 264 |
-
</div>
|
| 265 |
-
</div>
|
| 266 |
-
</div>
|
| 267 |
-
</div>
|
| 268 |
-
|
| 269 |
-
<!-- Success/Error Messages -->
|
| 270 |
-
<div class="toast-container position-fixed bottom-0 end-0 p-3">
|
| 271 |
-
<div id="success-toast" class="toast" role="alert">
|
| 272 |
-
<div class="toast-header bg-success text-white">
|
| 273 |
-
<i class="fas fa-check-circle me-2"></i>
|
| 274 |
-
<strong class="me-auto">نجح</strong>
|
| 275 |
-
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
|
| 276 |
-
</div>
|
| 277 |
-
<div class="toast-body" id="success-message"></div>
|
| 278 |
-
</div>
|
| 279 |
-
|
| 280 |
-
<div id="error-toast" class="toast" role="alert">
|
| 281 |
-
<div class="toast-header bg-danger text-white">
|
| 282 |
-
<i class="fas fa-exclamation-circle me-2"></i>
|
| 283 |
-
<strong class="me-auto">خطأ</strong>
|
| 284 |
-
<button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast"></button>
|
| 285 |
-
</div>
|
| 286 |
-
<div class="toast-body" id="error-message"></div>
|
| 287 |
-
</div>
|
| 288 |
-
</div>
|
| 289 |
-
|
| 290 |
-
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
| 291 |
-
<script src="/static/js/model-manager.js"></script>
|
| 292 |
-
</body>
|
| 293 |
-
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
templates/index.html
CHANGED
|
@@ -32,10 +32,10 @@
|
|
| 32 |
<span>Medical Datasets</span>
|
| 33 |
<small>Specialized medical data</small>
|
| 34 |
</a>
|
| 35 |
-
<a href="
|
| 36 |
<i class="fab fa-google"></i>
|
| 37 |
<span>Google Models</span>
|
| 38 |
-
<small>
|
| 39 |
</a>
|
| 40 |
<a href="#system-info" class="nav-link" onclick="showSystemInfo()">
|
| 41 |
<i class="fas fa-microchip"></i>
|
|
|
|
| 32 |
<span>Medical Datasets</span>
|
| 33 |
<small>Specialized medical data</small>
|
| 34 |
</a>
|
| 35 |
+
<a href="#google-models" class="nav-link" onclick="showGoogleModels()">
|
| 36 |
<i class="fab fa-google"></i>
|
| 37 |
<span>Google Models</span>
|
| 38 |
+
<small>Open source models</small>
|
| 39 |
</a>
|
| 40 |
<a href="#system-info" class="nav-link" onclick="showSystemInfo()">
|
| 41 |
<i class="fas fa-microchip"></i>
|
تقرير_التطوير_النهائي_والتكامل.md
DELETED
|
@@ -1,256 +0,0 @@
|
|
| 1 |
-
# تقرير التطوير النهائي والتكامل الشامل
|
| 2 |
-
## منصة تقطير المعرفة للذكاء الاصطناعي الطبي
|
| 3 |
-
|
| 4 |
-
**تاريخ التقرير:** 26 أغسطس 2024
|
| 5 |
-
**الحالة:** مكتمل - جاهز للنشر على Hugging Face Spaces
|
| 6 |
-
**الإصدار:** 2.0 - النسخة الوظيفية الكاملة
|
| 7 |
-
|
| 8 |
-
---
|
| 9 |
-
|
| 10 |
-
## ملخص التطوير المنجز
|
| 11 |
-
|
| 12 |
-
### 🎯 الأهداف المحققة
|
| 13 |
-
|
| 14 |
-
✅ **إصلاح المشاكل الحرجة:**
|
| 15 |
-
- حل مشكلة Loss = 0.0000 نهائياً
|
| 16 |
-
- إصلاح إدارة جلسات التدريب
|
| 17 |
-
- حل مشكلة WebSocket PosixPath serialization
|
| 18 |
-
|
| 19 |
-
✅ **تطوير نظام إدارة قواعد البيانات الطبية:**
|
| 20 |
-
- Backend APIs وظيفية بالكامل
|
| 21 |
-
- Frontend تفاعلي مع قاعدة بيانات SQLite
|
| 22 |
-
- تكامل مع الصفحة الرئيسية
|
| 23 |
-
|
| 24 |
-
✅ **تطوير نظام إدارة النماذج:**
|
| 25 |
-
- صفحة Google Models وظيفية
|
| 26 |
-
- نظام اختيار النماذج المعلمة والطلابية
|
| 27 |
-
- APIs متكاملة مع قاعدة البيانات
|
| 28 |
-
|
| 29 |
-
✅ **تحسين التكامل العام:**
|
| 30 |
-
- ربط جميع المكونات
|
| 31 |
-
- تحسين تجربة المستخدم
|
| 32 |
-
- إضافة معالجة شاملة للأخطاء
|
| 33 |
-
|
| 34 |
-
---
|
| 35 |
-
|
| 36 |
-
## التحسينات الرئيسية المنجزة
|
| 37 |
-
|
| 38 |
-
### 1. إصلاح نظام التدريب الحرجي
|
| 39 |
-
|
| 40 |
-
#### المشكلة السابقة:
|
| 41 |
-
```python
|
| 42 |
-
# البيانات العشوائية القديمة
|
| 43 |
-
data['text'] = torch.randn(512) # عشوائي تماماً
|
| 44 |
-
```
|
| 45 |
-
|
| 46 |
-
#### الحل المطور:
|
| 47 |
-
```python
|
| 48 |
-
# البيانات المنظمة الجديدة
|
| 49 |
-
def _create_text_patterns(self):
|
| 50 |
-
patterns = []
|
| 51 |
-
for i in range(10):
|
| 52 |
-
pattern = torch.randn(512)
|
| 53 |
-
pattern[0:50] = torch.sigmoid(pattern[0:50]) # بداية منظمة
|
| 54 |
-
pattern[-50:] = torch.tanh(pattern[-50:]) # نهاية منظمة
|
| 55 |
-
patterns.append(pattern)
|
| 56 |
-
return patterns
|
| 57 |
-
```
|
| 58 |
-
|
| 59 |
-
#### النتائج:
|
| 60 |
-
- **Loss حقيقي ومتغير** بدلاً من 0.0000
|
| 61 |
-
- **تعلم فعلي** مع تحسن تدريجي
|
| 62 |
-
- **مراقبة مفصلة** للتقدم
|
| 63 |
-
|
| 64 |
-
### 2. نظام إدارة الجلسات المحسن
|
| 65 |
-
|
| 66 |
-
#### الميزات الجديدة:
|
| 67 |
-
```python
|
| 68 |
-
# APIs إدارة الجلسات
|
| 69 |
-
GET /api/sessions # قائمة الجلسات
|
| 70 |
-
DELETE /api/sessions/{id} # حذف جلسة
|
| 71 |
-
POST /api/sessions/{id}/cancel # إلغاء جلسة
|
| 72 |
-
POST /api/sessions/cleanup # تنظيف شامل
|
| 73 |
-
```
|
| 74 |
-
|
| 75 |
-
#### التحسينات:
|
| 76 |
-
- **إعادة استخدام ذكية** للجلسات المكتملة
|
| 77 |
-
- **تنظيف تلقائي** للجلسات القديمة
|
| 78 |
-
- **مراقبة حالة** في الوقت الفعلي
|
| 79 |
-
|
| 80 |
-
### 3. نظام قواعد البيانات الطبية الوظيفي
|
| 81 |
-
|
| 82 |
-
#### قواعد البيانات المدعومة:
|
| 83 |
-
- **ROCOv2 Radiology** (8.5 GB, 81K عينة)
|
| 84 |
-
- **CT-RATE** (12.3 GB, 50K عينة)
|
| 85 |
-
- **UMIE Medical Datasets** (15.7 GB, 120K عينة)
|
| 86 |
-
|
| 87 |
-
#### الميزات المطورة:
|
| 88 |
-
- **اختيار تفاعلي** مع تصفية حسب التخصص
|
| 89 |
-
- **حفظ تلقائي** للاختيارات
|
| 90 |
-
- **تحقق من صحة** البيانات المختارة
|
| 91 |
-
- **توصيات ذكية** حسب التخصص
|
| 92 |
-
|
| 93 |
-
### 4. نظام إدارة النماذج المتقدم
|
| 94 |
-
|
| 95 |
-
#### النماذج المدعومة:
|
| 96 |
-
- **FLAN-T5 Base/Large** للنصوص
|
| 97 |
-
- **Vision Transformer** للصور
|
| 98 |
-
- **CLIP** متعدد الوسائط
|
| 99 |
-
- **BERT** للمهام الأساسية
|
| 100 |
-
|
| 101 |
-
#### الوظائف المطورة:
|
| 102 |
-
- **إضافة نماذج مخصصة** من أي مصدر
|
| 103 |
-
- **اختيار النموذج الطلابي** (جديد أو موجود)
|
| 104 |
-
- **حفظ التكوين** مع استرجاع تلقائي
|
| 105 |
-
- **تصفية وبحث** متقدم
|
| 106 |
-
|
| 107 |
-
---
|
| 108 |
-
|
| 109 |
-
## الهيكل التقني المطور
|
| 110 |
-
|
| 111 |
-
### Backend APIs الجديدة
|
| 112 |
-
|
| 113 |
-
#### إدارة الجلسات:
|
| 114 |
-
```
|
| 115 |
-
GET /api/sessions # قائمة الجلسات
|
| 116 |
-
DELETE /api/sessions/{id} # حذف جلسة
|
| 117 |
-
POST /api/sessions/{id}/cancel # إلغاء جلسة
|
| 118 |
-
POST /api/sessions/cleanup # تنظيف شامل
|
| 119 |
-
```
|
| 120 |
-
|
| 121 |
-
#### إدارة البيانات الطبية:
|
| 122 |
-
```
|
| 123 |
-
GET /api/medical-datasets # قائمة البيانات
|
| 124 |
-
POST /api/medical-datasets/select # حفظ الاختيارات
|
| 125 |
-
GET /api/medical-datasets/selections/{session} # استرجاع الاختيارات
|
| 126 |
-
DELETE /api/medical-datasets/selections/{session}/{dataset} # حذف اختيار
|
| 127 |
-
GET /api/medical-datasets/recommendations/{session} # توصيات
|
| 128 |
-
```
|
| 129 |
-
|
| 130 |
-
#### إدارة النماذج:
|
| 131 |
-
```
|
| 132 |
-
GET /api/google-models # قائمة النماذج
|
| 133 |
-
POST /api/model-configuration/save # حفظ التكوين
|
| 134 |
-
GET /api/model-configuration/{session} # استرجاع التكوين
|
| 135 |
-
DELETE /api/model-configuration/{session} # مسح التكوين
|
| 136 |
-
```
|
| 137 |
-
|
| 138 |
-
### قاعدة البيانات المطورة
|
| 139 |
-
|
| 140 |
-
#### الجداول الجديدة:
|
| 141 |
-
```sql
|
| 142 |
-
-- اختيارات قواعد البيانات الطبية
|
| 143 |
-
medical_dataset_selections (
|
| 144 |
-
id, user_session, dataset_name,
|
| 145 |
-
dataset_config, selected_at, is_active, selection_metadata
|
| 146 |
-
)
|
| 147 |
-
|
| 148 |
-
-- تفضيلات المستخدم
|
| 149 |
-
user_medical_preferences (
|
| 150 |
-
id, user_session, preferred_specialties, experience_level,
|
| 151 |
-
preferred_languages, training_preferences, created_at, updated_at
|
| 152 |
-
)
|
| 153 |
-
|
| 154 |
-
-- جلسات التدريب الطبي
|
| 155 |
-
medical_training_sessions (
|
| 156 |
-
id, session_id, user_session, selected_datasets,
|
| 157 |
-
training_config, medical_metrics, status, created_at, completed_at
|
| 158 |
-
)
|
| 159 |
-
```
|
| 160 |
-
|
| 161 |
-
### Frontend المحسن
|
| 162 |
-
|
| 163 |
-
#### الصفحات الجديدة:
|
| 164 |
-
- **`/google-models`** - إدارة النماذج المعلمة والطلابية
|
| 165 |
-
- **`/medical-datasets`** - محسنة بالكامل مع تفاعل حقيقي
|
| 166 |
-
|
| 167 |
-
#### JavaScript المطور:
|
| 168 |
-
- **`model-manager.js`** - إدارة شاملة للنماذج
|
| 169 |
-
- **`medical-datasets.js`** - محسن مع APIs حقيقية
|
| 170 |
-
|
| 171 |
-
---
|
| 172 |
-
|
| 173 |
-
## خطة النشر والتشغيل
|
| 174 |
-
|
| 175 |
-
### 1. متطلبات Hugging Face Spaces
|
| 176 |
-
|
| 177 |
-
#### الملفات المطلوبة:
|
| 178 |
-
```
|
| 179 |
-
app.py # التطبيق الرئيسي ✅
|
| 180 |
-
requirements.txt # المتطلبات ✅
|
| 181 |
-
README.md # الوثائق ✅
|
| 182 |
-
```
|
| 183 |
-
|
| 184 |
-
#### التحقق من التوافق:
|
| 185 |
-
- ✅ **الذاكرة:** محسن للعمل ضمن حدود HF Spaces
|
| 186 |
-
- ✅ **المعالجة:** تدريب متدرج مع إيقاف تلقائي
|
| 187 |
-
- ✅ **التخزين:** قاعدة بيانات SQLite محلية
|
| 188 |
-
|
| 189 |
-
### 2. خطوات النشر
|
| 190 |
-
|
| 191 |
-
#### الخطوة 1: التحقق النهائي
|
| 192 |
-
```bash
|
| 193 |
-
# تشغيل اختبار محلي
|
| 194 |
-
python app.py
|
| 195 |
-
|
| 196 |
-
# التحقق من APIs
|
| 197 |
-
curl http://localhost:7860/api/medical-datasets
|
| 198 |
-
curl http://localhost:7860/api/google-models
|
| 199 |
-
```
|
| 200 |
-
|
| 201 |
-
#### الخطوة 2: النشر على HF Spaces
|
| 202 |
-
```bash
|
| 203 |
-
# رفع الملفات
|
| 204 |
-
git add .
|
| 205 |
-
git commit -m "النسخة الوظيفية الكاملة 2.0"
|
| 206 |
-
git push origin main
|
| 207 |
-
```
|
| 208 |
-
|
| 209 |
-
#### الخطوة 3: التحقق من التشغيل
|
| 210 |
-
- ✅ تحميل الصفحة الرئيسية
|
| 211 |
-
- ✅ عمل صفحة البيانات الطبية
|
| 212 |
-
- ✅ عمل صفحة النماذج
|
| 213 |
-
- ✅ بدء جلسة تدريب تجريبية
|
| 214 |
-
|
| 215 |
-
### 3. المراقبة والصيانة
|
| 216 |
-
|
| 217 |
-
#### مؤشرات الأداء:
|
| 218 |
-
- **استخدام الذاكرة:** < 4GB
|
| 219 |
-
- **زمن الاستجابة:** < 2 ثانية للصفحات
|
| 220 |
-
- **معدل نجاح التدريب:** > 95%
|
| 221 |
-
|
| 222 |
-
#### الصيانة الدورية:
|
| 223 |
-
- **تنظيف قاعدة البيانات:** كل 30 يوم
|
| 224 |
-
- **تحديث النماذج:** شهرياً
|
| 225 |
-
- **مراجعة الأداء:** أسبوعياً
|
| 226 |
-
|
| 227 |
-
---
|
| 228 |
-
|
| 229 |
-
## الخطوات التالية المقترحة
|
| 230 |
-
|
| 231 |
-
### المرحلة القادمة (الأسبوع القادم):
|
| 232 |
-
1. **اختبار شامل** على HF Spaces
|
| 233 |
-
2. **تحسين الأداء** حسب الاستخدام الفعلي
|
| 234 |
-
3. **إضافة المزيد من النماذج** المدعومة
|
| 235 |
-
4. **تطوير نظام التقييم** للنماذج المدربة
|
| 236 |
-
|
| 237 |
-
### التطوير المستقبلي:
|
| 238 |
-
1. **دعم نماذج إضافية** (Anthropic, OpenAI)
|
| 239 |
-
2. **تحسين خوارزميات التقطير**
|
| 240 |
-
3. **إضافة نماذج تقييم طبية** متخصصة
|
| 241 |
-
4. **تطوير واجهة API** للاستخدام الخارجي
|
| 242 |
-
|
| 243 |
-
---
|
| 244 |
-
|
| 245 |
-
## الخلاصة
|
| 246 |
-
|
| 247 |
-
تم تطوير المنصة بنجاح من مجرد واجهة عرض إلى **نظام تدريب وظيفي بالكامل** يتضمن:
|
| 248 |
-
|
| 249 |
-
✅ **نظام تدريب حقيقي** مع Loss متغير وتعلم فعلي
|
| 250 |
-
✅ **إدارة متقدمة للجلسات** مع معالجة شاملة للأخطاء
|
| 251 |
-
✅ **نظام قواعد بيانات طبية** وظيفي ومتكامل
|
| 252 |
-
✅ **إدارة شاملة للنماذج** المعلمة والطلابية
|
| 253 |
-
✅ **تكامل سلس** بين جميع المكونات
|
| 254 |
-
✅ **توافق كامل** مع Hugging Face Spaces
|
| 255 |
-
|
| 256 |
-
المنصة جاهزة الآن للنشر والاستخدام الفعلي في تدريب نماذج الذكاء الاصطناعي الطبي.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|