1yahoo commited on
Commit
555501d
·
verified ·
1 Parent(s): 501f392

Upload folder using huggingface_hub

Browse files
.env.example ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Hugging Face API
2
+ HF_API_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
3
+ HF_MODEL_ID=mistralai/Mistral-7B-Instruct-v0.2
4
+
5
+ # Supabase (اختياري - يمكن استخدام ملفات JSON محلية بدلاً منه)
6
+ SUPABASE_URL=https://xxxxxxxxxxxxx.supabase.co
7
+ SUPABASE_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxx
8
+
9
+ # Application Settings
10
+ APP_NAME=مدرستك المحورية
11
+ APP_DESCRIPTION=رحلة تعلم مخصصة على مدار 3 أشهر
12
+ DEFAULT_LANGUAGE=ar
README.md CHANGED
@@ -1,12 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
- title: Edu App Ai
3
- emoji: 🌍
4
- colorFrom: purple
5
- colorTo: gray
6
- sdk: gradio
7
- sdk_version: 6.4.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
1
+ # 🎓 مدرستك المحورية - Personalized Learning Hub
2
+
3
+ <div dir="rtl">
4
+
5
+ ## 🌟 نظرة عامة
6
+
7
+ **مدرستك المحورية** هي منصة تعليمية ذكية مبنية بتقنية Streamlit ومدعومة بنماذج Hugging Face، توفر لك تجربة تعلم مخصصة بالكامل على مدار 3 أشهر (12 أسبوعاً). المنصة تتكيف مع مستواك، وقتك المتاح، وأسلوب تعلمك المفضل.
8
+
9
+ ## ✨ المميزات الرئيسية
10
+
11
+ ### 📝 ملف تعريفي مخصص
12
+ - إنشاء ملف تعريفي شامل (مهارات، مستوى، وقت متاح، أهداف)
13
+ - رفع ملف JSON أو ملء نموذج تفاعلي
14
+ - معاينة وتعديل الملف في أي وقت
15
+
16
+ ### 🎯 خطط تعليمية ذكية
17
+ - اختيار من 4 محاور تعليمية:
18
+ - 🤖 **الذكاء الاصطناعي**: أساسيات AI والتعلم الآلي
19
+ - 📊 **البيانات والمنطق**: تحليل البيانات والتفكير النقدي
20
+ - 🎨 **التفكير التصميمي**: حل المشكلات بإبداع
21
+ - 💻 **تطوير الويب**: بناء مواقع حديثة
22
+ - توليد منهج مخصص لـ 12 أسبوعاً
23
+ - جدول زمني يتكيف مع ساعاتك المتاحة
24
+
25
+ ### 📚 دروس تفاعلية
26
+ - دروس قصيرة ومكثفة (5-10 دقائق)
27
+ - محتوى يتكيف مع أسلوب تعلمك
28
+ - تمارين عملية وأسئلة للتفكير
29
+ - نظام ملاحظات شخصية
30
+
31
+ ### 🔬 تجارب عملية
32
+ - تجربة عملية لكل أسبوع
33
+ - رفع المخرجات والملفات
34
+ - توثيق التعلم والتحديات
35
+ - بناء محفظة أعمال
36
+
37
+ ### 👥 مشاريع جماعية
38
+ - إنشاء والانضمام لمشاريع
39
+ - إدارة المهام وتعيينها
40
+ - رفع مخرجات المشروع
41
+ - التعاون مع متعلمين آخرين
42
+
43
+ ### 📊 متابعة التقدم
44
+ - لوحة تحكم شاملة للتقدم
45
+ - رسوم بيانية تفاعلية (Plotly)
46
+ - نظام شارات وإنجازات
47
+ - تصدير المحفظة بصيغة JSON
48
+
49
+ ## 🚀 التثبيت والتشغيل
50
+
51
+ ### المتطلبات
52
+ - Python 3.8+
53
+ - pip
54
+
55
+ ### خطوات التشغيل محلياً
56
+
57
+ 1. **استنساخ المشروع**
58
+ ```bash
59
+ git clone <repository-url>
60
+ cd huggingface-space-repo
61
+ ```
62
+
63
+ 2. **تثبيت المكتبات**
64
+ ```bash
65
+ pip install -r requirements.txt
66
+ ```
67
+
68
+ 3. **إعداد متغيرات البيئة** (اختياري)
69
+ ```bash
70
+ cp .env.example .env
71
+ # عدّل الملف وأضف مفاتيح API إن وُجدت
72
+ ```
73
+
74
+ 4. **تشغيل التطبيق**
75
+ ```bash
76
+ streamlit run app.py
77
+ ```
78
+
79
+ 5. **فتح المتصفح**
80
+ ```
81
+ http://localhost:8501
82
+ ```
83
+
84
+ ## 📁 هيكل المشروع
85
+
86
+ ```
87
+ huggingface-space-repo/
88
+ ├── app.py # التطبيق الرئيسي
89
+ ├── config.py # الإعدادات العامة
90
+ ├── requirements.txt # المكتبات المطلوبة
91
+ ├── .env.example # مثال لمتغيرات البيئة
92
+
93
+ ├── utils/ # الوظائف المساعدة
94
+ │ ├── api_utils.py # تكامل HF API و Supabase
95
+ │ ├── data_utils.py # معالجة البيانات
96
+ │ └── ui_utils.py # تخصيص الواجهة
97
+
98
+ ├── pages/ # صفحات التطبيق
99
+ │ ├── home.py # الصفحة الرئيسية
100
+ │ ├── profile.py # الملف التعريفي
101
+ │ ├── plan.py # الخطة التعليمية
102
+ │ ├── lesson.py # الدروس
103
+ │ ├── experiment.py # التجارب العملية
104
+ │ ├── projects.py # المشاريع الجماعية
105
+ │ └── progress.py # التقدم والمحفظة
106
+
107
+ ├── assets/ # الأصول البصرية
108
+ │ └── css/
109
+ │ └── style.css # التنسيقات المخصصة
110
+
111
+ ├── data/ # البيانات الثابتة
112
+ │ ├── sample_profiles.json
113
+ │ ├── units.json
114
+ │ └── prompts.json
115
+
116
+ └── tests/ # الاختبارات
117
+ └── test_app.py
118
+ ```
119
+
120
+ ## 🎨 التخصيص
121
+
122
+ ### الألوان والثيم
123
+ عدّل ملف `config.py` لتغيير الألوان:
124
+ ```python
125
+ COLORS = {
126
+ "primary": "#667eea",
127
+ "secondary": "#764ba2",
128
+ # ...
129
+ }
130
+ ```
131
+
132
+ ### إضافة محاور جديدة
133
+ في `config.py`، أضف محور جديد:
134
+ ```python
135
+ LEARNING_HUBS = {
136
+ "new_hub": {
137
+ "name": "المحور الجديد",
138
+ "description": "وصف المحور",
139
+ "duration_weeks": 12,
140
+ "estimated_hours": 120,
141
+ "icon": "🎓"
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## 🔗 التكامل مع Hugging Face
147
+
148
+ ### استخدام نموذج مخصص
149
+ 1. احصل على API Token من Hugging Face
150
+ 2. أضفه في `.env`:
151
+ ```
152
+ HF_API_TOKEN=hf_xxxxxxxxxxxxx
153
+ HF_MODEL_ID=mistralai/Mistral-7B-Instruct-v0.2
154
+ ```
155
+
156
+ ### استخدام Supabase (اختياري)
157
+ للحفظ الدائم للبيانات:
158
+ ```
159
+ SUPABASE_URL=https://xxxxx.supabase.co
160
+ SUPABASE_KEY=xxxxxxxxxxxxx
161
+ ```
162
+
163
+ ## 🚀 النشر على Hugging Face Spaces
164
+
165
+ 1. **إنشاء Space جديد**
166
+ - اذهب إلى: https://huggingface.co/spaces
167
+ - انقر "Create new Space"
168
+ - اختر: Streamlit SDK
169
+
170
+ 2. **رفع الملفات**
171
+ ```bash
172
+ git clone https://huggingface.co/spaces/<username>/<space-name>
173
+ cd <space-name>
174
+
175
+ # انسخ جميع الملفات
176
+ cp -r /path/to/huggingface-space-repo/* .
177
+
178
+ git add .
179
+ git commit -m "Initial commit"
180
+ git push
181
+ ```
182
+
183
+ 3. **إعداد Secrets**
184
+ - في إعدادات Space، أضف:
185
+ - `HF_API_TOKEN`
186
+ - `SUPABASE_URL` (إن استخدمت)
187
+ - `SUPABASE_KEY` (إن استخدمت)
188
+
189
+ 4. **التشغيل**
190
+ - Space سيعمل تلقائياً!
191
+
192
+ ## 📝 الاستخدام
193
+
194
+ ### 1. إنشاء ملف تعريفي
195
+ - انتقل لصفحة "الملف التعريفي"
196
+ - املأ معلوماتك (الاسم، المستوى، الوقت المتاح...)
197
+ - أو ارفع ملف JSON
198
+
199
+ ### 2. اختيار محور
200
+ - اذهب لصفحة "الخطة التعليمية"
201
+ - اختر المحور المناسب لك
202
+ - انقر "توليد الخطة المخصصة"
203
+
204
+ ### 3. البدء بالتعلم
205
+ - افتح "الدروس"
206
+ - اختر أسبوعاً
207
+ - ابدأ بقراءة الدروس
208
+ - ضع علامة "مكتمل" عند الانتهاء
209
+
210
+ ### 4. التجارب العملية
211
+ - افتح "التجارب"
212
+ - اختر تجربة من أسبوع معين
213
+ - قم برفع مخرجاتك
214
+
215
+ ### 5. المشاريع الجماعية
216
+ - أنشئ مشروعاً أو انضم لواحد
217
+ - أضف مهام وأعضاء
218
+ - ارفع مخرجات المشروع
219
+
220
+ ## 🤝 المساهمة
221
+
222
+ نرحب بالمساهمات! لإضافة ميزات جديدة:
223
+
224
+ 1. Fork المشروع
225
+ 2. أنشئ فرعاً جديداً (`git checkout -b feature/AmazingFeature`)
226
+ 3. Commit تغييراتك (`git commit -m 'Add some AmazingFeature'`)
227
+ 4. Push للفرع (`git push origin feature/AmazingFeature`)
228
+ 5. افتح Pull Request
229
+
230
+ ## 📄 الترخيص
231
+
232
+ هذا المشروع مفتوح المصدر تحت ترخيص MIT.
233
+
234
+ ## 📧 التواصل
235
+
236
+ لأي استفسارات أو مشاكل، يرجى فتح Issue على GitHub.
237
+
238
+ ## 🙏 شكر وتقدير
239
+
240
+ - **Streamlit** - إطار العمل الرائع
241
+ - **Hugging Face** - نماذج الذكاء الاصطناعي
242
+ - **Plotly** - الرسوم البيانية التفاعلية
243
+ - **المجتمع العربي** - الإلهام والدعم
244
+
245
  ---
 
 
 
 
 
 
 
 
 
246
 
247
+ **بُني بـ ❤️ لتمكين المتعلمين العرب في كل مكان** 🎓
248
+
249
+ </div>
app.py ADDED
@@ -0,0 +1,140 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ التطبيق الرئيسي - مدرستك المحورية
3
+ نسخة بدون تسجيل دخول (MVP)
4
+ """
5
+
6
+ import streamlit as st
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ # إضافة المسار الحالي
11
+ sys.path.append(str(Path(__file__).parent))
12
+
13
+ import config
14
+ from utils.ui_utils import load_custom_css, init_session_state
15
+
16
+ # استيراد الصفحات
17
+ from pages import home, profile, plan, lesson, experiment, projects, progress
18
+
19
+
20
+ def main():
21
+ """الدالة الرئيسية للتطبيق"""
22
+
23
+ # إعدادات الصفحة
24
+ st.set_page_config(
25
+ page_title=config.APP_NAME,
26
+ page_icon="🎓",
27
+ layout="wide",
28
+ initial_sidebar_state="expanded"
29
+ )
30
+
31
+ # تحميل CSS مخصص
32
+ load_custom_css()
33
+
34
+ # تهيئة session state
35
+ init_session_state({
36
+ "page": "home",
37
+ "profile": None,
38
+ "selected_hub": None,
39
+ "curriculum": None,
40
+ "schedule": None,
41
+ "completed_lessons": [],
42
+ "completed_experiments": [],
43
+ "completed_units": [],
44
+ "projects": [],
45
+ "user_projects": [],
46
+ "experiment_submissions": {},
47
+ "selected_week_idx": 0
48
+ })
49
+
50
+ # Sidebar للتنقل
51
+ with st.sidebar:
52
+ st.markdown(f"# {config.APP_NAME}")
53
+ st.markdown("---")
54
+
55
+ # قائمة التنقل
56
+ st.markdown("### 📍 التنقل")
57
+
58
+ pages = {
59
+ "home": {"name": "🏠 الصفحة الرئيسية", "icon": "🏠"},
60
+ "profile": {"name": "👤 الملف التعريفي", "icon": "👤"},
61
+ "plan": {"name": "📅 الخطة التعليمية", "icon": "📅"},
62
+ "lesson": {"name": "📚 الدروس", "icon": "📚"},
63
+ "experiment": {"name": "🔬 التجارب", "icon": "🔬"},
64
+ "projects": {"name": "👥 المشاريع", "icon": "👥"},
65
+ "progress": {"name": "📊 التقدم", "icon": "📊"}
66
+ }
67
+
68
+ for page_key, page_data in pages.items():
69
+ if st.button(
70
+ page_data['name'],
71
+ key=f"nav_{page_key}",
72
+ use_container_width=True,
73
+ type="primary" if st.session_state.page == page_key else "secondary"
74
+ ):
75
+ st.session_state.page = page_key
76
+ st.rerun()
77
+
78
+ st.markdown("---")
79
+
80
+ # معلومات سريعة
81
+ if st.session_state.profile:
82
+ st.markdown("### 👤 معلومات سريعة")
83
+ st.markdown(f"**الاسم:** {st.session_state.profile.get('name', 'غير محدد')}")
84
+ st.markdown(f"**المستوى:** {st.session_state.profile.get('level', 'مبتدئ')}")
85
+
86
+ if st.session_state.curriculum:
87
+ total_lessons = sum(len(unit.get("lessons", [])) for unit in st.session_state.curriculum)
88
+ completed = len(st.session_state.completed_lessons)
89
+ percentage = (completed / total_lessons * 100) if total_lessons > 0 else 0
90
+
91
+ st.markdown(f"**التقدم:** {percentage:.1f}%")
92
+ st.progress(percentage / 100)
93
+
94
+ st.markdown("---")
95
+
96
+ # معلومات التطبيق
97
+ st.markdown("### ℹ️ معلومات")
98
+ st.markdown(f"""
99
+ **الإصدار:** 1.0.0 (MVP)
100
+ **النوع:** بدون تسجيل دخول
101
+ **الحالة:** تجريبي
102
+
103
+ ---
104
+
105
+ 🤗 **مدعوم بـ Hugging Face**
106
+ 💻 **مبني بـ Streamlit**
107
+ """)
108
+
109
+ # زر إعادة تعيين (للتجربة)
110
+ st.markdown("---")
111
+ if st.button("🔄 إعادة تعيين التطبيق", use_container_width=True):
112
+ for key in list(st.session_state.keys()):
113
+ del st.session_state[key]
114
+ st.rerun()
115
+
116
+ # عرض الصفحة المحددة
117
+ try:
118
+ if st.session_state.page == "home":
119
+ home.show()
120
+ elif st.session_state.page == "profile":
121
+ profile.show()
122
+ elif st.session_state.page == "plan":
123
+ plan.show()
124
+ elif st.session_state.page == "lesson":
125
+ lesson.show()
126
+ elif st.session_state.page == "experiment":
127
+ experiment.show()
128
+ elif st.session_state.page == "projects":
129
+ projects.show()
130
+ elif st.session_state.page == "progress":
131
+ progress.show()
132
+ else:
133
+ home.show()
134
+ except Exception as e:
135
+ st.error(f"حدث خطأ: {str(e)}")
136
+ st.info("يرجى المحاولة مرة أخرى أو إعادة تعيين التطبيق من الشريط الجانبي")
137
+
138
+
139
+ if __name__ == "__main__":
140
+ main()
assets/css/style.css ADDED
@@ -0,0 +1,295 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ملف CSS مخصص للتطبيق */
2
+
3
+ /* استيراد خط عربي جميل */
4
+ @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@300;400;500;700;800&display=swap');
5
+
6
+ /* تطبيق الخط على كل العناصر */
7
+ * {
8
+ font-family: 'Tajawal', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif !important;
9
+ direction: rtl;
10
+ text-align: right;
11
+ }
12
+
13
+ /* خلفية التطبيق */
14
+ .main {
15
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
16
+ background-attachment: fixed;
17
+ }
18
+
19
+ /* حاوية المحتوى */
20
+ .block-container {
21
+ background: rgba(255, 255, 255, 0.97);
22
+ border-radius: 25px;
23
+ padding: 2.5rem;
24
+ margin-top: 2rem;
25
+ margin-bottom: 2rem;
26
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
27
+ backdrop-filter: blur(10px);
28
+ }
29
+
30
+ /* العناوين */
31
+ h1, h2, h3, h4, h5, h6 {
32
+ color: #667eea;
33
+ font-weight: 700;
34
+ }
35
+
36
+ h1 {
37
+ font-size: 2.5rem !important;
38
+ }
39
+
40
+ /* الأزرار */
41
+ .stButton > button {
42
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
43
+ color: white;
44
+ border: none;
45
+ border-radius: 12px;
46
+ padding: 0.75rem 2rem;
47
+ font-size: 1.05rem;
48
+ font-weight: 600;
49
+ transition: all 0.3s ease;
50
+ width: 100%;
51
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
52
+ }
53
+
54
+ .stButton > button:hover {
55
+ transform: translateY(-3px);
56
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.5);
57
+ }
58
+
59
+ .stButton > button:active {
60
+ transform: translateY(-1px);
61
+ }
62
+
63
+ /* الأزرار الثانوية */
64
+ .stButton > button[kind="secondary"] {
65
+ background: white;
66
+ color: #667eea;
67
+ border: 2px solid #667eea;
68
+ }
69
+
70
+ .stButton > button[kind="secondary"]:hover {
71
+ background: #f7fafc;
72
+ }
73
+
74
+ /* صناديق المعلومات */
75
+ .success-box {
76
+ background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
77
+ color: white;
78
+ padding: 1.25rem;
79
+ border-radius: 12px;
80
+ margin: 1.5rem 0;
81
+ box-shadow: 0 4px 15px rgba(72, 187, 120, 0.3);
82
+ }
83
+
84
+ .info-box {
85
+ background: linear-gradient(135deg, #4299e1 0%, #3182ce 100%);
86
+ color: white;
87
+ padding: 1.25rem;
88
+ border-radius: 12px;
89
+ margin: 1.5rem 0;
90
+ box-shadow: 0 4px 15px rgba(66, 153, 225, 0.3);
91
+ }
92
+
93
+ .warning-box {
94
+ background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%);
95
+ color: white;
96
+ padding: 1.25rem;
97
+ border-radius: 12px;
98
+ margin: 1.5rem 0;
99
+ box-shadow: 0 4px 15px rgba(237, 137, 54, 0.3);
100
+ }
101
+
102
+ /* البطاقات */
103
+ .card {
104
+ background: white;
105
+ border-radius: 15px;
106
+ padding: 1.75rem;
107
+ margin: 1.25rem 0;
108
+ box-shadow: 0 5px 20px rgba(0, 0, 0, 0.08);
109
+ transition: all 0.3s ease;
110
+ border: 1px solid #e2e8f0;
111
+ }
112
+
113
+ .card:hover {
114
+ transform: translateY(-5px);
115
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.12);
116
+ }
117
+
118
+ /* شريط التقدم */
119
+ .progress-bar {
120
+ background: #e2e8f0;
121
+ border-radius: 12px;
122
+ height: 30px;
123
+ overflow: hidden;
124
+ margin: 1.5rem 0;
125
+ box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.1);
126
+ }
127
+
128
+ .progress-fill {
129
+ background: linear-gradient(90deg, #48bb78 0%, #38a169 100%);
130
+ height: 100%;
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: center;
134
+ color: white;
135
+ font-weight: 700;
136
+ font-size: 0.95rem;
137
+ transition: width 0.6s ease;
138
+ box-shadow: 0 2px 8px rgba(72, 187, 120, 0.4);
139
+ }
140
+
141
+ /* الشارات */
142
+ .badge {
143
+ display: inline-block;
144
+ padding: 0.4rem 0.9rem;
145
+ border-radius: 25px;
146
+ font-size: 0.85rem;
147
+ font-weight: 600;
148
+ margin: 0.25rem;
149
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
150
+ }
151
+
152
+ .badge-primary {
153
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
154
+ color: white;
155
+ }
156
+
157
+ .badge-success {
158
+ background: #48bb78;
159
+ color: white;
160
+ }
161
+
162
+ .badge-warning {
163
+ background: #ed8936;
164
+ color: white;
165
+ }
166
+
167
+ .badge-info {
168
+ background: #4299e1;
169
+ color: white;
170
+ }
171
+
172
+ .badge-danger {
173
+ background: #f56565;
174
+ color: white;
175
+ }
176
+
177
+ /* حقول الإدخال */
178
+ .stTextInput > div > div > input,
179
+ .stTextArea > div > div > textarea,
180
+ .stSelectbox > div > div > select {
181
+ border-radius: 10px;
182
+ border: 2px solid #e2e8f0;
183
+ padding: 0.75rem;
184
+ font-size: 1rem;
185
+ transition: all 0.3s ease;
186
+ }
187
+
188
+ .stTextInput > div > div > input:focus,
189
+ .stTextArea > div > div > textarea:focus,
190
+ .stSelectbox > div > div > select:focus {
191
+ border-color: #667eea;
192
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
193
+ }
194
+
195
+ /* Expander */
196
+ .streamlit-expanderHeader {
197
+ background: #f7fafc;
198
+ border-radius: 10px;
199
+ font-weight: 600;
200
+ padding: 1rem;
201
+ border: 1px solid #e2e8f0;
202
+ }
203
+
204
+ .streamlit-expanderHeader:hover {
205
+ background: #edf2f7;
206
+ }
207
+
208
+ /* Tabs */
209
+ .stTabs [data-baseweb="tab-list"] {
210
+ gap: 0.5rem;
211
+ }
212
+
213
+ .stTabs [data-baseweb="tab"] {
214
+ border-radius: 10px 10px 0 0;
215
+ padding: 0.75rem 1.5rem;
216
+ font-weight: 600;
217
+ }
218
+
219
+ .stTabs [aria-selected="true"] {
220
+ background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
221
+ color: white;
222
+ }
223
+
224
+ /* Metrics */
225
+ .stMetric {
226
+ background: white;
227
+ padding: 1rem;
228
+ border-radius: 10px;
229
+ box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
230
+ }
231
+
232
+ /* Sidebar */
233
+ .css-1d391kg, [data-testid="stSidebar"] {
234
+ background: linear-gradient(180deg, #667eea 0%, #764ba2 100%);
235
+ color: white;
236
+ }
237
+
238
+ .css-1d391kg .stButton > button, [data-testid="stSidebar"] .stButton > button {
239
+ background: rgba(255, 255, 255, 0.2);
240
+ color: white;
241
+ border: 1px solid rgba(255, 255, 255, 0.3);
242
+ }
243
+
244
+ .css-1d391kg .stButton > button:hover, [data-testid="stSidebar"] .stButton > button:hover {
245
+ background: rgba(255, 255, 255, 0.3);
246
+ }
247
+
248
+ /* تنسيقات إضافية */
249
+ hr {
250
+ border: none;
251
+ border-top: 2px solid #e2e8f0;
252
+ margin: 2rem 0;
253
+ }
254
+
255
+ /* تحسين المسافات */
256
+ .element-container {
257
+ margin-bottom: 1rem;
258
+ }
259
+
260
+ /* رسائل Streamlit */
261
+ .stAlert {
262
+ border-radius: 10px;
263
+ border: none;
264
+ padding: 1rem;
265
+ }
266
+
267
+ /* File Uploader */
268
+ .stFileUploader {
269
+ border: 2px dashed #cbd5e0;
270
+ border-radius: 10px;
271
+ padding: 2rem;
272
+ transition: all 0.3s ease;
273
+ }
274
+
275
+ .stFileUploader:hover {
276
+ border-color: #667eea;
277
+ background: #f7fafc;
278
+ }
279
+
280
+ /* تحسينات للجوال */
281
+ @media (max-width: 768px) {
282
+ .block-container {
283
+ padding: 1.5rem;
284
+ margin-top: 1rem;
285
+ }
286
+
287
+ h1 {
288
+ font-size: 2rem !important;
289
+ }
290
+
291
+ .stButton > button {
292
+ padding: 0.6rem 1rem;
293
+ font-size: 0.95rem;
294
+ }
295
+ }
config.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ملف الإعدادات العامة للتطبيق
3
+ """
4
+ import os
5
+ from dotenv import load_dotenv
6
+
7
+ # تحميل متغيرات البيئة
8
+ load_dotenv()
9
+
10
+ # إعدادات Hugging Face
11
+ HF_API_TOKEN = os.getenv("HF_API_TOKEN", "")
12
+ HF_MODEL_ID = os.getenv("HF_MODEL_ID", "mistralai/Mistral-7B-Instruct-v0.2")
13
+ HF_API_URL = f"https://api-inference.huggingface.co/models/{HF_MODEL_ID}"
14
+
15
+ # إعدادات Supabase (اختياري)
16
+ SUPABASE_URL = os.getenv("SUPABASE_URL", "")
17
+ SUPABASE_KEY = os.getenv("SUPABASE_KEY", "")
18
+ USE_SUPABASE = bool(SUPABASE_URL and SUPABASE_KEY)
19
+
20
+ # إعدادات التطبيق
21
+ APP_NAME = "🎓 مدرستك المحورية"
22
+ APP_DESCRIPTION = "رحلة تعلم مخصصة على مدار 3 أشهر"
23
+ DEFAULT_LANGUAGE = "ar"
24
+
25
+ # إعدادات الخطط
26
+ PLAN_DURATION_WEEKS = 12
27
+ DEFAULT_HOURS_PER_WEEK = 10
28
+
29
+ # ألوان التطبيق (Theme)
30
+ COLORS = {
31
+ "primary": "#667eea",
32
+ "secondary": "#764ba2",
33
+ "success": "#48bb78",
34
+ "warning": "#ed8936",
35
+ "danger": "#f56565",
36
+ "info": "#4299e1",
37
+ "light": "#f7fafc",
38
+ "dark": "#2d3748",
39
+ }
40
+
41
+ # أيقونات
42
+ ICONS = {
43
+ "home": "🏠",
44
+ "profile": "👤",
45
+ "plan": "📅",
46
+ "lesson": "📚",
47
+ "experiment": "🔬",
48
+ "projects": "👥",
49
+ "progress": "📊",
50
+ "completed": "✅",
51
+ "pending": "⏳",
52
+ "locked": "🔒",
53
+ }
54
+
55
+ # محاور التعلم (Hubs) المتاحة
56
+ LEARNING_HUBS = {
57
+ "ai_intro": {
58
+ "name": "مقدمة في الذكاء الاصطناعي",
59
+ "description": "تعلم أساسيات الذكاء الاصطناعي والتعلم الآلي",
60
+ "duration_weeks": 12,
61
+ "estimated_hours": 120,
62
+ "icon": "🤖"
63
+ },
64
+ "data_logic": {
65
+ "name": "البيانات والمنطق",
66
+ "description": "تحليل البيانات والتفكير المنطقي",
67
+ "duration_weeks": 12,
68
+ "estimated_hours": 100,
69
+ "icon": "📊"
70
+ },
71
+ "design_thinking": {
72
+ "name": "التفكير التصميمي",
73
+ "description": "حل المشكلات بطريقة إبداعية",
74
+ "duration_weeks": 12,
75
+ "estimated_hours": 90,
76
+ "icon": "🎨"
77
+ },
78
+ "web_development": {
79
+ "name": "تطوير الويب",
80
+ "description": "بناء مواقع وتطبيقات ويب حديثة",
81
+ "duration_weeks": 12,
82
+ "estimated_hours": 150,
83
+ "icon": "💻"
84
+ },
85
+ }
data/prompts.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "curriculum_generation": {
3
+ "system": "أنت مصمم مناهج تعليمية خبير. مهمتك توليد خطة تعلم لمدة {weeks} أسبوعاً لمستخدم بناءً على ملفه التعريفي.",
4
+ "user_template": "معلومات المستخدم:\n- الاسم: {name}\n- المستوى: {level}\n- الوقت المتاح أسبوعياً: {available_hours} ساعات\n- أسلوب التعلم المفضل: {learning_style}\n- المحور المطلوب: {hub_name}\n\nولّد خطة تعلم بصيغة JSON تحتوي على {weeks} وحدة أسبوعية. كل وحدة يجب أن تحتوي على:\n- week: رقم الأسبوع\n- unit_title: عنوان الوحدة\n- estimated_time_mins: الوقت المقدر بالدقائق\n- lessons: قائمة بالدروس (كل درس له: title, duration_mins, type, description)\n- experiment: تجربة عملية (title, description, deliverable)\n\nردّ فقط بصيغة JSON بدون أي نص إضافي."
5
+ },
6
+
7
+ "lesson_generation": {
8
+ "system": "أنت مدرس متخصص يكتب دروساً تعليمية قصيرة (5-10 دقائق قراءة) لفهم مفهوم واحد عميق.",
9
+ "user_template": "موضوع الدرس: {lesson_title}\n\nالدرس يجب أن يتضمن:\n1. الفكرة المركزية (ملخص في سطرين)\n2. شرح مبسط مع أمثلة واقعية\n3. نقاط رئيسية (3-5 نقاط)\n4. سؤال للتفكير\n5. مقترحات للمتابعة\n\nاكتب بأسلوب واضح ومشجع ومناسب لمستوى: {level}"
10
+ },
11
+
12
+ "lesson_adaptation": {
13
+ "system": "أنت مدرس يتكيف مع أسلوب المتعلم.",
14
+ "user_template": "المستخدم يفضل الأسلوب: {preferred_style}\n\nأعد كتابة الدرس التالي بطريقة تناسب هذا الأسلوب:\n\n{lesson_content}\n\nتأكد من:\n- إذا كان بصرياً: استخدم أوصافاً مرئية وأمثلة مصورة\n- إذا كان نصياً: ركز على التفاصيل المكتوبة والشروحات الدقيقة\n- إذا كان عملياً: أكثر من الأمثلة التطبيقية والتمارين\n- إذا كان مختلطاً: امزج بين كل الأساليب"
15
+ },
16
+
17
+ "exercise_generation": {
18
+ "system": "أنت مصمم تمارين تعليمية مبتكرة.",
19
+ "user_template": "اصنع تمريناً يختبر الفهم الحقيقي للمفهوم التالي: {concept}\n\nالتمرين يجب أن:\n- لا يعتمد فقط على الحفظ\n- يتطلب تطبيق عملي أو تفكير نقدي\n- يكون مناسباً لمستوى: {level}\n- يستغرق حوالي {duration} دقيقة\n\nأرفق معيار تصحيح مختصر."
20
+ },
21
+
22
+ "project_suggestion": {
23
+ "system": "أنت مستشار مشاريع تعليمية.",
24
+ "user_template": "اقترح {count} أفكار مشاريع جماعية مناسبة للمحور: {hub_name}\n\nكل مشروع يجب أن يتضمن:\n- العنوان\n- الوصف (2-3 جمل)\n- المهارات المطلوبة\n- المدة المقترحة (بالأسابيع)\n- المخرجات المتوقعة\n- عدد الأعضاء المناسب\n\nالمشاريع يجب أن تكون:\n- عملية وقابلة للتنفيذ\n- مناسبة لمستوى: {level}\n- تعزز التعلم التعاوني"
25
+ },
26
+
27
+ "feedback_generation": {
28
+ "system": "أنت معلم يقدم تغذية راجعة بناءة ومشجعة.",
29
+ "user_template": "الطالب أكمل التمرين/المشروع التالي:\n\n{submission}\n\nقدم تغذية راجعة تتضمن:\n1. نقاط القوة (2-3 نقاط)\n2. مجالات التحسين (2-3 نقاط)\n3. اقتراحات محددة للتطوير\n4. كلمة تشجيعية\n\nكن إيجابياً وداعماً مع الحفاظ على الموضوعية."
30
+ }
31
+ }
data/sample_profiles.json ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "sample_profiles": [
3
+ {
4
+ "name": "أحمد محمد",
5
+ "age": "25",
6
+ "level": "متوسط",
7
+ "skills": ["Python", "تحليل البيانات", "Excel"],
8
+ "available_hours": 15,
9
+ "learning_style": "مختلط (نظري وعملي)",
10
+ "goals": "أريد تعلم الذكاء الاصطناعي والتعلم الآلي لبناء تطبيقات ذكية وتحسين مهاراتي في مجال البيانات",
11
+ "preferences": {
12
+ "language": "ar",
13
+ "notifications": true,
14
+ "theme": "light"
15
+ }
16
+ },
17
+ {
18
+ "name": "سارة علي",
19
+ "age": "22",
20
+ "level": "مبتدئ",
21
+ "skills": ["تصميم جرافيك", "Photoshop"],
22
+ "available_hours": 10,
23
+ "learning_style": "بصري (فيديوهات، صور)",
24
+ "goals": "أرغب في تعلم التفكير التصميمي وتطوير مهاراتي الإبداعية لحل المشكلات بطرق مبتكرة",
25
+ "preferences": {
26
+ "language": "ar",
27
+ "notifications": true,
28
+ "theme": "light"
29
+ }
30
+ },
31
+ {
32
+ "name": "محمد حسن",
33
+ "age": "30",
34
+ "level": "متقدم",
35
+ "skills": ["JavaScript", "React", "Node.js", "HTML", "CSS"],
36
+ "available_hours": 20,
37
+ "learning_style": "عملي (تطبيقات، مشاريع)",
38
+ "goals": "أسعى لتطوير مهاراتي في تطوير الويب الحديث وبناء تطبيقات متقدمة",
39
+ "preferences": {
40
+ "language": "ar",
41
+ "notifications": true,
42
+ "theme": "dark"
43
+ }
44
+ }
45
+ ]
46
+ }
data/units.json ADDED
@@ -0,0 +1,187 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "ai_intro": {
3
+ "hub_id": "ai_intro",
4
+ "name": "مقدمة في الذكاء الاصطناعي",
5
+ "description": "تعلم أساسيات الذكاء الاصطناعي والتعلم الآلي",
6
+ "icon": "🤖",
7
+ "sample_units": [
8
+ {
9
+ "week": 1,
10
+ "unit_title": "ما هو الذكاء الاصطناعي؟",
11
+ "estimated_time_mins": 480,
12
+ "lessons": [
13
+ {
14
+ "title": "تعريف الذكاء الاصطناعي",
15
+ "duration_mins": 60,
16
+ "type": "concept",
17
+ "description": "فهم المفاهيم الأساسية للذكاء الاصطناعي وتطبيقاته"
18
+ },
19
+ {
20
+ "title": "تاريخ الذكاء الاصطناعي",
21
+ "duration_mins": 90,
22
+ "type": "theory",
23
+ "description": "رحلة عبر تطور AI من الخمسينات حتى اليوم"
24
+ },
25
+ {
26
+ "title": "أنواع الذكاء الاصطناعي",
27
+ "duration_mins": 120,
28
+ "type": "concept",
29
+ "description": "التعرف على AI الضيق، العام، والفائق"
30
+ }
31
+ ],
32
+ "experiment": {
33
+ "title": "استكشاف تطبيقات AI في حياتك",
34
+ "description": "قم بتوثيق 10 تطبيقات ذكاء اصطناعي تستخدمها يومياً وكيف تعمل",
35
+ "deliverable": "تقرير مكتوب أو عرض تقديمي"
36
+ }
37
+ },
38
+ {
39
+ "week": 2,
40
+ "unit_title": "أساسيات التعلم الآلي",
41
+ "estimated_time_mins": 540,
42
+ "lessons": [
43
+ {
44
+ "title": "مقدمة في Machine Learning",
45
+ "duration_mins": 90,
46
+ "type": "concept",
47
+ "description": "فهم الفرق بين البرمجة التقليدية والتعلم الآلي"
48
+ },
49
+ {
50
+ "title": "أنواع التعلم الآلي",
51
+ "duration_mins": 120,
52
+ "type": "theory",
53
+ "description": "التعلم الموجه، غير الموجه، والتعزيزي"
54
+ },
55
+ {
56
+ "title": "أول نموذج تعلم آلي",
57
+ "duration_mins": 180,
58
+ "type": "practical",
59
+ "description": "بناء نموذج بسيط للتنبؤ باستخدام Python"
60
+ }
61
+ ],
62
+ "experiment": {
63
+ "title": "بناء نموذج تصنيف بسيط",
64
+ "description": "استخدم مكتبة scikit-learn لبناء نموذج يصنف البيانات",
65
+ "deliverable": "كود Python + شرح للنتائج"
66
+ }
67
+ }
68
+ ]
69
+ },
70
+
71
+ "data_logic": {
72
+ "hub_id": "data_logic",
73
+ "name": "البيانات والمنطق",
74
+ "description": "تحليل البيانات والتفكير المنطقي",
75
+ "icon": "📊",
76
+ "sample_units": [
77
+ {
78
+ "week": 1,
79
+ "unit_title": "مقدمة في تحليل البيانات",
80
+ "estimated_time_mins": 450,
81
+ "lessons": [
82
+ {
83
+ "title": "ما هي البيانات؟",
84
+ "duration_mins": 60,
85
+ "type": "concept",
86
+ "description": "فهم أنواع البيانات ومصادرها"
87
+ },
88
+ {
89
+ "title": "أدوات تحليل البيانات",
90
+ "duration_mins": 120,
91
+ "type": "practical",
92
+ "description": "التعرف على Excel, Python, وأدوات أخرى"
93
+ },
94
+ {
95
+ "title": "التفكير النقدي والبيانات",
96
+ "duration_mins": 90,
97
+ "type": "theory",
98
+ "description": "كيف نقرأ البيانات بشكل نقدي"
99
+ }
100
+ ],
101
+ "experiment": {
102
+ "title": "تحليل مجموعة بيانات حقيقية",
103
+ "description": "اختر مجموعة بيانات من Kaggle وقم بتحليلها",
104
+ "deliverable": "تقرير تحليلي + رسوم بيانية"
105
+ }
106
+ }
107
+ ]
108
+ },
109
+
110
+ "design_thinking": {
111
+ "hub_id": "design_thinking",
112
+ "name": "التفكير التصميمي",
113
+ "description": "حل المشكلات بطريقة إبداعية",
114
+ "icon": "🎨",
115
+ "sample_units": [
116
+ {
117
+ "week": 1,
118
+ "unit_title": "مبادئ التفكير التصميمي",
119
+ "estimated_time_mins": 420,
120
+ "lessons": [
121
+ {
122
+ "title": "ما هو التفكير التصميمي؟",
123
+ "duration_mins": 60,
124
+ "type": "concept",
125
+ "description": "فهم العملية الإبداعية لحل المشكلات"
126
+ },
127
+ {
128
+ "title": "المراحل الخمس",
129
+ "duration_mins": 120,
130
+ "type": "theory",
131
+ "description": "التعاطف، التعريف، التخيل، النمذجة، الاختبار"
132
+ },
133
+ {
134
+ "title": "التعاطف مع المستخدم",
135
+ "duration_mins": 90,
136
+ "type": "practical",
137
+ "description": "تقنيات لفهم احتياجات المستخدمين"
138
+ }
139
+ ],
140
+ "experiment": {
141
+ "title": "مشروع تصميم حل لمشكلة محلية",
142
+ "description": "حدد مشكلة في محيطك وطبق عملية التفكير التصميمي",
143
+ "deliverable": "عرض تقديمي للحل المقترح"
144
+ }
145
+ }
146
+ ]
147
+ },
148
+
149
+ "web_development": {
150
+ "hub_id": "web_development",
151
+ "name": "تطوير الويب",
152
+ "description": "بناء مواقع وتطبيقات ويب حديثة",
153
+ "icon": "💻",
154
+ "sample_units": [
155
+ {
156
+ "week": 1,
157
+ "unit_title": "أساسيات HTML و CSS",
158
+ "estimated_time_mins": 600,
159
+ "lessons": [
160
+ {
161
+ "title": "مقدمة في HTML",
162
+ "duration_mins": 120,
163
+ "type": "concept",
164
+ "description": "بنية صفحة الويب والعناصر الأساسية"
165
+ },
166
+ {
167
+ "title": "تنسيق الصفحات بـ CSS",
168
+ "duration_mins": 180,
169
+ "type": "practical",
170
+ "description": "الألوان، الخطوط، والتخطيط"
171
+ },
172
+ {
173
+ "title": "التصميم المتجاوب",
174
+ "duration_mins": 120,
175
+ "type": "practical",
176
+ "description": "بناء صفحات تعمل على جميع الأجهزة"
177
+ }
178
+ ],
179
+ "experiment": {
180
+ "title": "بناء صفحة ويب شخصية",
181
+ "description": "صمم وطور صفحة ويب تعريفية عنك",
182
+ "deliverable": "موقع ويب منشور على GitHub Pages"
183
+ }
184
+ }
185
+ ]
186
+ }
187
+ }
pages/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # ملف لجعل pages مجلداً قابلاً للاستيراد
pages/experiment.py ADDED
@@ -0,0 +1,223 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ صفحة التجارب العملية - رفع المخرجات وتوثيق التجارب
3
+ """
4
+ import streamlit as st
5
+ import sys
6
+ sys.path.append('..')
7
+ from utils.ui_utils import load_custom_css, show_success_message, show_warning_message
8
+ from datetime import datetime
9
+
10
+
11
+ def show():
12
+ """عرض صفحة التجارب العملية"""
13
+
14
+ load_custom_css()
15
+
16
+ st.markdown("## 🔬 التجارب العملية")
17
+ st.markdown("---")
18
+
19
+ # تهيئة completed_experiments
20
+ if "completed_experiments" not in st.session_state:
21
+ st.session_state.completed_experiments = []
22
+
23
+ if "experiment_submissions" not in st.session_state:
24
+ st.session_state.experiment_submissions = {}
25
+
26
+ # إذا كان هناك تجربة محددة
27
+ if "current_experiment" in st.session_state and st.session_state.current_experiment:
28
+ experiment = st.session_state.current_experiment
29
+ week = st.session_state.get("current_week", 0)
30
+
31
+ st.markdown(f"""
32
+ <div class='card' style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;'>
33
+ <h2 style='color: white; margin-top: 0;'>🔬 {experiment['title']}</h2>
34
+ <p><strong>الأسبوع:</strong> {week}</p>
35
+ <p style='font-size: 1.1rem;'>{experiment['description']}</p>
36
+ <p><strong>📦 المخرج المطلوب:</strong> {experiment['deliverable']}</p>
37
+ </div>
38
+ """, unsafe_allow_html=True)
39
+
40
+ st.markdown("---")
41
+
42
+ experiment_id = f"week_{week}_{experiment['title']}"
43
+ is_completed = experiment_id in st.session_state.completed_experiments
44
+
45
+ if is_completed:
46
+ show_success_message("✅ لقد أكملت هذه التجربة!")
47
+
48
+ # عرض التسليم السابق
49
+ if experiment_id in st.session_state.experiment_submissions:
50
+ submission = st.session_state.experiment_submissions[experiment_id]
51
+
52
+ st.markdown("### 📄 تسليمك السابق:")
53
+ st.markdown(f"""
54
+ <div class='card'>
55
+ <p><strong>تاريخ التسليم:</strong> {submission.get('date', 'غير محدد')}</p>
56
+ <p><strong>الملاحظات:</strong></p>
57
+ <p>{submission.get('notes', 'لا توجد ملاحظات')}</p>
58
+ </div>
59
+ """, unsafe_allow_html=True)
60
+
61
+ if submission.get('files'):
62
+ st.markdown("**📎 الملفات المرفقة:**")
63
+ for file_name in submission['files']:
64
+ st.markdown(f"- 📄 {file_name}")
65
+
66
+ else:
67
+ # نموذج التسليم
68
+ st.markdown("### 📤 تسليم التجربة")
69
+
70
+ st.markdown("""
71
+ <div class='info-box'>
72
+ <strong>💡 نصائح للتسليم الناجح:</strong>
73
+ <ul style='margin: 0.5rem 0 0 0;'>
74
+ <li>وثّق خطوات عملك بوضوح</li>
75
+ <li>اشرح التحديات التي واجهتك وكيف حللتها</li>
76
+ <li>ضع تأملاتك وما تعلمته</li>
77
+ <li>أرفق ملفات أو روابط ذات صلة</li>
78
+ </ul>
79
+ </div>
80
+ """, unsafe_allow_html=True)
81
+
82
+ st.markdown("<br>", unsafe_allow_html=True)
83
+
84
+ # حقول التسليم
85
+ notes = st.text_area(
86
+ "📝 وصف عملك والملاحظات:",
87
+ placeholder="اشرح ما قمت به، الخطوات، التحديات، وما تعلمته...",
88
+ height=200
89
+ )
90
+
91
+ st.markdown("---")
92
+
93
+ # رفع ملفات
94
+ st.markdown("### 📎 رفع الملفات (اختياري)")
95
+ uploaded_files = st.file_uploader(
96
+ "ارفع ملفات المشروع (صور، مستندات، كود...)",
97
+ accept_multiple_files=True,
98
+ help="يمكنك رفع عدة ملفات"
99
+ )
100
+
101
+ # أو إضافة روابط
102
+ st.markdown("### 🔗 أو أضف روابط")
103
+ links = st.text_area(
104
+ "روابط (GitHub, Google Drive, إلخ)",
105
+ placeholder="https://github.com/username/project\nhttps://drive.google.com/...",
106
+ height=100
107
+ )
108
+
109
+ st.markdown("<br>", unsafe_allow_html=True)
110
+
111
+ # زر التسليم
112
+ col1, col2 = st.columns([2, 1])
113
+ with col1:
114
+ if st.button("✅ تسليم التجربة", use_container_width=True, type="primary"):
115
+ if notes.strip():
116
+ # حفظ التسليم
117
+ submission = {
118
+ "date": datetime.now().strftime("%Y-%m-%d %H:%M"),
119
+ "notes": notes,
120
+ "files": [f.name for f in uploaded_files] if uploaded_files else [],
121
+ "links": links.strip().split("\n") if links.strip() else []
122
+ }
123
+
124
+ st.session_state.experiment_submissions[experiment_id] = submission
125
+ st.session_state.completed_experiments.append(experiment_id)
126
+
127
+ show_success_message("🎉 تم تسليم التجربة بنجاح!")
128
+ st.balloons()
129
+ st.rerun()
130
+ else:
131
+ show_warning_message("⚠️ يجب كتابة وصف للعمل على الأقل")
132
+
133
+ with col2:
134
+ if st.button("💾 حفظ مسودة"):
135
+ st.info("تم حفظ عملك كمسودة")
136
+
137
+ # زر العودة
138
+ st.markdown("<br><br>", unsafe_allow_html=True)
139
+ if st.button("⬅️ العودة للدروس"):
140
+ st.session_state.page = "lesson"
141
+ st.rerun()
142
+
143
+ else:
144
+ # عرض جميع التجارب
145
+ st.markdown("### 🔬 جميع التجارب العملية")
146
+
147
+ if "curriculum" not in st.session_state or not st.session_state.curriculum:
148
+ st.warning("⚠️ لم يتم إنشاء خطة تعليمية بعد!")
149
+ if st.button("📅 إنشاء خطة"):
150
+ st.session_state.page = "plan"
151
+ st.rerun()
152
+ return
153
+
154
+ curriculum = st.session_state.curriculum
155
+ experiments = [(unit['week'], unit.get('experiment')) for unit in curriculum if unit.get('experiment')]
156
+
157
+ # حساب التقدم
158
+ total_experiments = len(experiments)
159
+ completed_count = len(st.session_state.completed_experiments)
160
+ progress_percentage = (completed_count / total_experiments * 100) if total_experiments > 0 else 0
161
+
162
+ col1, col2, col3 = st.columns(3)
163
+ with col1:
164
+ st.metric("إجمالي التجارب", total_experiments)
165
+ with col2:
166
+ st.metric("مكتملة", completed_count)
167
+ with col3:
168
+ st.metric("النسبة", f"{progress_percentage:.1f}%")
169
+
170
+ st.markdown("---")
171
+
172
+ # عرض التجارب
173
+ for week, experiment in experiments:
174
+ experiment_id = f"week_{week}_{experiment['title']}"
175
+ is_completed = experiment_id in st.session_state.completed_experiments
176
+
177
+ status_icon = "✅" if is_completed else "⏳"
178
+ status_color = "#48bb78" if is_completed else "#ed8936"
179
+
180
+ with st.expander(f"{status_icon} الأسبوع {week}: {experiment['title']}"):
181
+ st.markdown(f"""
182
+ <div class='card' style='border-right: 4px solid {status_color};'>
183
+ <p><strong>الوصف:</strong> {experiment['description']}</p>
184
+ <p><strong>📦 المخرج:</strong> {experiment['deliverable']}</p>
185
+ </div>
186
+ """, unsafe_allow_html=True)
187
+
188
+ if is_completed:
189
+ st.success("مكتمل ✅")
190
+
191
+ # عرض التسليم
192
+ if experiment_id in st.session_state.experiment_submissions:
193
+ submission = st.session_state.experiment_submissions[experiment_id]
194
+ with st.expander("📄 عرض التسليم"):
195
+ st.markdown(f"**تاريخ التسليم:** {submission.get('date', 'غير محدد')}")
196
+ st.markdown("**الملاحظات:**")
197
+ st.markdown(submission.get('notes', 'لا توجد'))
198
+
199
+ if submission.get('files'):
200
+ st.markdown("**الملفات:**")
201
+ for file_name in submission['files']:
202
+ st.markdown(f"- {file_name}")
203
+
204
+ if submission.get('links'):
205
+ st.markdown("**الروابط:**")
206
+ for link in submission['links']:
207
+ if link.strip():
208
+ st.markdown(f"- [{link}]({link})")
209
+ else:
210
+ if st.button(f"🚀 ابدأ التجربة", key=f"start_exp_{week}", use_container_width=True):
211
+ st.session_state.current_experiment = experiment
212
+ st.session_state.current_week = week
213
+ st.rerun()
214
+
215
+ # زر العودة
216
+ st.markdown("<br><br>", unsafe_allow_html=True)
217
+ if st.button("⬅️ العودة للدروس"):
218
+ st.session_state.page = "lesson"
219
+ st.rerun()
220
+
221
+
222
+ if __name__ == "__main__":
223
+ show()
pages/home.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ الصفحة الرئيسية - شرح التطبيق وزر البدء
3
+ """
4
+ import streamlit as st
5
+ import sys
6
+ sys.path.append('..')
7
+ import config
8
+ from utils.ui_utils import show_hero_section, show_feature_cards, load_custom_css
9
+
10
+
11
+ def show():
12
+ """عرض صفحة البداية"""
13
+
14
+ # تحميل CSS مخصص
15
+ load_custom_css()
16
+
17
+ # قسم Hero
18
+ show_hero_section(
19
+ config.APP_NAME,
20
+ config.APP_DESCRIPTION
21
+ )
22
+
23
+ # وصف مختصر
24
+ st.markdown("""
25
+ <div style='text-align: center; max-width: 800px; margin: 2rem auto;'>
26
+ <p style='font-size: 1.1rem; color: #4a5568; line-height: 1.8;'>
27
+ مرحباً بك في منصة تعليمية ذكية تُصمم لك خطة تعلم مخصصة بناءً على مهاراتك،
28
+ وقتك المتاح، وأهدافك. ستحصل على منهج شامل لمدة 12 أسبوعاً مع دروس تفاعلية،
29
+ تجارب عملية، ومشاريع جماعية لتعزيز تعلمك.
30
+ </p>
31
+ </div>
32
+ """, unsafe_allow_html=True)
33
+
34
+ st.markdown("<br>", unsafe_allow_html=True)
35
+
36
+ # بطاقات الميزات
37
+ features = [
38
+ {
39
+ "icon": "🎯",
40
+ "title": "خطة مخصصة",
41
+ "description": "منهج يتكيف مع مستواك ووقتك المتاح"
42
+ },
43
+ {
44
+ "icon": "📚",
45
+ "title": "دروس تفاعلية",
46
+ "description": "محتوى تعليمي غني بالأمثلة والتطبيقات"
47
+ },
48
+ {
49
+ "icon": "🔬",
50
+ "title": "تجارب عملية",
51
+ "description": "مشاريع تطبيقية لتعزيز الفهم"
52
+ },
53
+ {
54
+ "icon": "📊",
55
+ "title": "متابعة التقدم",
56
+ "description": "تتبع إنجازاتك ومحفظة أعمالك"
57
+ }
58
+ ]
59
+
60
+ show_feature_cards(features)
61
+
62
+ st.markdown("<br><br>", unsafe_allow_html=True)
63
+
64
+ # كيف يعمل؟
65
+ st.markdown("""
66
+ <div style='max-width: 900px; margin: 3rem auto;'>
67
+ <h2 style='text-align: center; color: #667eea; margin-bottom: 2rem;'>
68
+ 🚀 كيف تبدأ رحلتك؟
69
+ </h2>
70
+ </div>
71
+ """, unsafe_allow_html=True)
72
+
73
+ # خطوات البدء
74
+ steps = [
75
+ {
76
+ "number": "1",
77
+ "title": "أنشئ ملفك التعريفي",
78
+ "description": "أخبرنا عن مهاراتك، مستواك، ووقتك المتاح",
79
+ "icon": "👤"
80
+ },
81
+ {
82
+ "number": "2",
83
+ "title": "اختر محورك التعليمي",
84
+ "description": "حدد المجال الذي تريد التعلم فيه (AI، البيانات، التصميم...)",
85
+ "icon": "🎓"
86
+ },
87
+ {
88
+ "number": "3",
89
+ "title": "احصل على خطتك المخصصة",
90
+ "description": "سنولّد لك منهجاً كاملاً لـ 12 أسبوعاً",
91
+ "icon": "📅"
92
+ },
93
+ {
94
+ "number": "4",
95
+ "title": "ابدأ التعلم والممارسة",
96
+ "description": "دروس يومية، تجارب عملية، ومشاريع مثيرة",
97
+ "icon": "🚀"
98
+ }
99
+ ]
100
+
101
+ for step in steps:
102
+ st.markdown(f"""
103
+ <div class='card' style='display: flex; align-items: center; gap: 1.5rem; margin: 1rem auto; max-width: 800px;'>
104
+ <div style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
105
+ color: white; width: 60px; height: 60px; border-radius: 50%;
106
+ display: flex; align-items: center; justify-content: center;
107
+ font-size: 1.5rem; font-weight: bold; flex-shrink: 0;'>
108
+ {step['number']}
109
+ </div>
110
+ <div style='flex: 1;'>
111
+ <h3 style='margin: 0 0 0.5rem 0; color: #2d3748;'>
112
+ {step['icon']} {step['title']}
113
+ </h3>
114
+ <p style='margin: 0; color: #718096;'>{step['description']}</p>
115
+ </div>
116
+ </div>
117
+ """, unsafe_allow_html=True)
118
+
119
+ st.markdown("<br><br>", unsafe_allow_html=True)
120
+
121
+ # زر البدء
122
+ col1, col2, col3 = st.columns([1, 2, 1])
123
+ with col2:
124
+ if st.button("🎯 ابدأ رحلتك التعليمية الآن", use_container_width=True, type="primary"):
125
+ st.session_state.page = "profile"
126
+ st.rerun()
127
+
128
+ st.markdown("<br>", unsafe_allow_html=True)
129
+
130
+ # معلومات إضافية
131
+ with st.expander("❓ أسئلة شائعة"):
132
+ st.markdown("""
133
+ ### هل التطبيق مجاني؟
134
+ نعم! هذه نسخة تجريبية مجانية بالكامل.
135
+
136
+ ### كم من الوقت أحتاج أسبوعياً؟
137
+ يمكنك تحديد الوقت المتاح لديك (من ساعة إلى 40 ساعة أسبوعياً) وسيتكيف المنهج معك.
138
+
139
+ ### هل أحتاج خبرة سابقة؟
140
+ لا! نوفر محاور للمبتدئين وللمتقدمين على حد سواء.
141
+
142
+ ### هل يمكنني تغيير الخطة لاحقاً؟
143
+ بالتأكيد! يمكنك تعديل خطتك في أي وقت.
144
+
145
+ ### هل هناك شهادات؟
146
+ حالياً لا نقدم شهادات رسمية، لكننا نوفر محفظة أعمال توثّق إنجازاتك.
147
+ """)
148
+
149
+ # Footer
150
+ st.markdown("<br><br>", unsafe_allow_html=True)
151
+ st.markdown("""
152
+ <div style='text-align: center; color: #a0aec0; padding: 2rem; border-top: 1px solid #e2e8f0; margin-top: 3rem;'>
153
+ <p>🎓 مدرستك المحورية | مدعوم بـ Hugging Face 🤗</p>
154
+ <p style='font-size: 0.9rem;'>بُني بـ ❤️ لتمكين المتعلمين في كل مكان</p>
155
+ </div>
156
+ """, unsafe_allow_html=True)
157
+
158
+
159
+ if __name__ == "__main__":
160
+ show()
pages/lesson.py ADDED
@@ -0,0 +1,233 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ صفحة الدرس اليومي - عرض الدروس والشرح
3
+ """
4
+ import streamlit as st
5
+ import sys
6
+ sys.path.append('..')
7
+ from utils.ui_utils import load_custom_css, show_success_message, show_lesson_card, show_progress_bar
8
+ from utils.api_utils import generate_lesson_content
9
+
10
+
11
+ def show():
12
+ """عرض صفحة الدروس"""
13
+
14
+ load_custom_css()
15
+
16
+ st.markdown("## 📚 الدروس التعليمية")
17
+ st.markdown("---")
18
+
19
+ # التحقق من وجود منهج
20
+ if "curriculum" not in st.session_state or not st.session_state.curriculum:
21
+ st.warning("⚠️ لم يتم إنشاء خطة تعليمية بعد!")
22
+ if st.button("📅 إنشاء خطة"):
23
+ st.session_state.page = "plan"
24
+ st.rerun()
25
+ return
26
+
27
+ curriculum = st.session_state.curriculum
28
+
29
+ # تهيئة الدروس المكتملة
30
+ if "completed_lessons" not in st.session_state:
31
+ st.session_state.completed_lessons = []
32
+
33
+ # حساب التقدم
34
+ total_lessons = sum(len(unit.get("lessons", [])) for unit in curriculum)
35
+ completed_count = len(st.session_state.completed_lessons)
36
+ progress_percentage = (completed_count / total_lessons * 100) if total_lessons > 0 else 0
37
+
38
+ # عرض التقدم
39
+ st.markdown("### 📊 تقدمك في الدروس")
40
+ col1, col2, col3 = st.columns(3)
41
+ with col1:
42
+ st.metric("الدروس المكتملة", f"{completed_count}/{total_lessons}")
43
+ with col2:
44
+ st.metric("نسبة الإنجاز", f"{progress_percentage:.1f}%")
45
+ with col3:
46
+ remaining = total_lessons - completed_count
47
+ st.metric("الدروس المتبقية", remaining)
48
+
49
+ show_progress_bar(progress_percentage, "")
50
+
51
+ st.markdown("---")
52
+
53
+ # اختيار الأسبوع
54
+ st.markdown("### 📅 اختر الأسبوع")
55
+
56
+ week_options = [f"الأسبوع {unit['week']}: {unit['unit_title']}" for unit in curriculum]
57
+
58
+ if "selected_week_idx" not in st.session_state:
59
+ st.session_state.selected_week_idx = 0
60
+
61
+ selected_week_display = st.selectbox(
62
+ "اختر أسبوعاً:",
63
+ week_options,
64
+ index=st.session_state.selected_week_idx
65
+ )
66
+
67
+ selected_week_idx = week_options.index(selected_week_display)
68
+ st.session_state.selected_week_idx = selected_week_idx
69
+
70
+ current_unit = curriculum[selected_week_idx]
71
+
72
+ st.markdown("---")
73
+
74
+ # عرض معلومات الوحدة
75
+ st.markdown(f"### 📖 {current_unit['unit_title']}")
76
+
77
+ col1, col2 = st.columns(2)
78
+ with col1:
79
+ st.markdown(f"""
80
+ <div class='card'>
81
+ <h4>⏱️ معلومات الوحدة</h4>
82
+ <p><strong>الأسبوع:</strong> {current_unit['week']}</p>
83
+ <p><strong>الوقت المقدر:</strong> {current_unit['estimated_time_mins']} دقيقة (~{round(current_unit['estimated_time_mins']/60, 1)} ساعة)</p>
84
+ <p><strong>عدد الدروس:</strong> {len(current_unit.get('lessons', []))}</p>
85
+ </div>
86
+ """, unsafe_allow_html=True)
87
+
88
+ with col2:
89
+ unit_lessons = current_unit.get("lessons", [])
90
+ unit_completed = sum(1 for lesson in unit_lessons if f"{current_unit['week']}_{lesson['title']}" in st.session_state.completed_lessons)
91
+ unit_progress = (unit_completed / len(unit_lessons) * 100) if len(unit_lessons) > 0 else 0
92
+
93
+ st.markdown(f"""
94
+ <div class='card'>
95
+ <h4>📊 تقدم هذه الوحدة</h4>
96
+ <p><strong>مكتمل:</strong> {unit_completed}/{len(unit_lessons)} دروس</p>
97
+ </div>
98
+ """, unsafe_allow_html=True)
99
+ show_progress_bar(unit_progress, "")
100
+
101
+ st.markdown("---")
102
+
103
+ # عرض الدروس
104
+ st.markdown("### 📚 دروس هذا الأسبوع")
105
+
106
+ lessons = current_unit.get("lessons", [])
107
+
108
+ if not lessons:
109
+ st.info("لا توجد دروس في هذه الوحدة")
110
+ else:
111
+ for idx, lesson in enumerate(lessons):
112
+ lesson_id = f"{current_unit['week']}_{lesson['title']}"
113
+ is_completed = lesson_id in st.session_state.completed_lessons
114
+
115
+ with st.expander(f"{'✅' if is_completed else '📖'} درس {idx+1}: {lesson['title']}", expanded=(idx==0 and not is_completed)):
116
+
117
+ # معلومات الدرس
118
+ col1, col2 = st.columns([3, 1])
119
+ with col1:
120
+ st.markdown(f"**الوصف:** {lesson.get('description', 'لا يوجد وصف')}")
121
+ with col2:
122
+ st.markdown(f"**⏱️ المدة:** {lesson.get('duration_mins', 0)} دقيقة")
123
+ st.markdown(f"**📌 النوع:** {lesson.get('type', 'درس')}")
124
+
125
+ st.markdown("---")
126
+
127
+ # محتوى الدرس
128
+ if f"lesson_content_{lesson_id}" not in st.session_state:
129
+ if st.button(f"📖 عرض محتوى الدرس", key=f"load_{lesson_id}"):
130
+ with st.spinner("🔄 جاري تحميل المحتوى..."):
131
+ content = generate_lesson_content(
132
+ lesson['title'],
133
+ st.session_state.get("profile", {})
134
+ )
135
+ st.session_state[f"lesson_content_{lesson_id}"] = content
136
+ st.rerun()
137
+ else:
138
+ # عرض المحتوى
139
+ content = st.session_state[f"lesson_content_{lesson_id}"]
140
+
141
+ st.markdown(f"""
142
+ <div class='card' style='background: #f7fafc;'>
143
+ {content}
144
+ </div>
145
+ """, unsafe_allow_html=True)
146
+
147
+ st.markdown("---")
148
+
149
+ # أزرار التفاعل
150
+ col1, col2, col3 = st.columns(3)
151
+
152
+ with col1:
153
+ if not is_completed:
154
+ if st.button("✅ وضع علامة مكتمل", key=f"complete_{lesson_id}", use_container_width=True):
155
+ st.session_state.completed_lessons.append(lesson_id)
156
+ show_success_message("تم تسجيل إتمام الدرس! 🎉")
157
+ st.balloons()
158
+ st.rerun()
159
+ else:
160
+ st.success("مكتمل ✅")
161
+
162
+ with col2:
163
+ if st.button("🔄 إعادة الشرح", key=f"reload_{lesson_id}", use_container_width=True):
164
+ del st.session_state[f"lesson_content_{lesson_id}"]
165
+ st.info("قم بالضغط على 'عرض محتوى الدرس' مرة أخرى")
166
+
167
+ with col3:
168
+ if st.button("📝 ملاحظات", key=f"notes_{lesson_id}", use_container_width=True):
169
+ st.session_state[f"show_notes_{lesson_id}"] = True
170
+
171
+ # منطقة الملاحظات
172
+ if st.session_state.get(f"show_notes_{lesson_id}", False):
173
+ st.markdown("**✍️ ملاحظاتك الشخصية:**")
174
+ note_key = f"note_{lesson_id}"
175
+ if note_key not in st.session_state:
176
+ st.session_state[note_key] = ""
177
+
178
+ note = st.text_area(
179
+ "اكتب ملاحظاتك هنا:",
180
+ value=st.session_state[note_key],
181
+ key=f"note_input_{lesson_id}",
182
+ height=100
183
+ )
184
+
185
+ if st.button("💾 حفظ الملاحظات", key=f"save_note_{lesson_id}"):
186
+ st.session_state[note_key] = note
187
+ show_success_message("تم حفظ ملاحظاتك!")
188
+
189
+ # التجربة العملية
190
+ if current_unit.get("experiment"):
191
+ st.markdown("---")
192
+ st.markdown("### 🔬 التجربة العملية للأسبوع")
193
+
194
+ experiment = current_unit["experiment"]
195
+
196
+ st.markdown(f"""
197
+ <div class='card' style='background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;'>
198
+ <h3 style='color: white; margin-top: 0;'>🔬 {experiment['title']}</h3>
199
+ <p>{experiment['description']}</p>
200
+ <p><strong>📦 المخرج المطلوب:</strong> {experiment['deliverable']}</p>
201
+ </div>
202
+ """, unsafe_allow_html=True)
203
+
204
+ if st.button("🚀 ابدأ التجربة", use_container_width=True, type="primary"):
205
+ st.session_state.current_experiment = experiment
206
+ st.session_state.current_week = current_unit['week']
207
+ st.session_state.page = "experiment"
208
+ st.rerun()
209
+
210
+ # أزرار التنقل
211
+ st.markdown("<br><br>", unsafe_allow_html=True)
212
+ st.markdown("---")
213
+
214
+ col1, col2, col3 = st.columns(3)
215
+
216
+ with col1:
217
+ if st.button("⬅️ العودة للخطة"):
218
+ st.session_state.page = "plan"
219
+ st.rerun()
220
+
221
+ with col2:
222
+ if st.button("📊 التقدم والإنجازات", use_container_width=True):
223
+ st.session_state.page = "progress"
224
+ st.rerun()
225
+
226
+ with col3:
227
+ if st.button("👥 المشاريع الجماعية", use_container_width=True):
228
+ st.session_state.page = "projects"
229
+ st.rerun()
230
+
231
+
232
+ if __name__ == "__main__":
233
+ show()
pages/plan.py ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ صفحة اختيار/توليد الخطة التعليمية
3
+ """
4
+ import streamlit as st
5
+ import sys
6
+ sys.path.append('..')
7
+ import config
8
+ from utils.ui_utils import load_custom_css, show_hub_selector, show_success_message, show_warning_message, show_progress_bar
9
+ from utils.api_utils import generate_curriculum, generate_default_curriculum
10
+ from utils.data_utils import generate_schedule
11
+
12
+
13
+ def show():
14
+ """عرض صفحة اختيار الخطة"""
15
+
16
+ load_custom_css()
17
+
18
+ st.markdown("## 📅 خطة التعلم المخصصة")
19
+ st.markdown("---")
20
+
21
+ # التحقق من وجود ملف تعريفي
22
+ if "profile" not in st.session_state or not st.session_state.profile:
23
+ show_warning_message("⚠️ يجب إنشاء ملف تعريفي أولاً!")
24
+ if st.button("👤 إنشاء ملف تعريفي"):
25
+ st.session_state.page = "profile"
26
+ st.rerun()
27
+ return
28
+
29
+ profile = st.session_state.profile
30
+
31
+ # عرض ملخص الملف التعريفي
32
+ st.markdown("### 👤 ملفك التعريفي")
33
+ col1, col2, col3 = st.columns(3)
34
+ with col1:
35
+ st.metric("الاسم", profile.get("name", "غير محدد"))
36
+ with col2:
37
+ st.metric("المستوى", profile.get("level", "مبتدئ"))
38
+ with col3:
39
+ st.metric("الوقت المتاح", f"{profile.get('available_hours', 10)} ساعة/أسبوع")
40
+
41
+ st.markdown("---")
42
+
43
+ # اختيار المحور
44
+ if "selected_hub" not in st.session_state:
45
+ st.session_state.selected_hub = None
46
+
47
+ if not st.session_state.selected_hub:
48
+ st.markdown("### 🎯 الخطوة 1: اختر محورك التعليمي")
49
+ st.markdown("اختر المجال الذي تريد التخصص فيه على مدار 3 أشهر:")
50
+
51
+ # عرض المحاور
52
+ cols = st.columns(2)
53
+ hub_items = list(config.LEARNING_HUBS.items())
54
+
55
+ for idx, (hub_id, hub_data) in enumerate(hub_items):
56
+ col = cols[idx % 2]
57
+ with col:
58
+ st.markdown(f"""
59
+ <div class='card' style='min-height: 220px;'>
60
+ <div style='text-align: center; font-size: 3rem; margin-bottom: 1rem;'>
61
+ {hub_data['icon']}
62
+ </div>
63
+ <h3 style='text-align: center; margin: 0.5rem 0;'>{hub_data['name']}</h3>
64
+ <p style='text-align: center; color: #718096;'>{hub_data['description']}</p>
65
+ <div style='text-align: center; margin-top: 1rem;'>
66
+ <span class='badge badge-info'>⏱️ {hub_data['duration_weeks']} أسبوع</span>
67
+ <span class='badge badge-primary'>📊 ~{hub_data['estimated_hours']} ساعة</span>
68
+ </div>
69
+ </div>
70
+ """, unsafe_allow_html=True)
71
+
72
+ if st.button(f"اختيار {hub_data['name']}", key=f"select_{hub_id}", use_container_width=True):
73
+ st.session_state.selected_hub = hub_id
74
+ st.rerun()
75
+
76
+ else:
77
+ # عرض المحور المختار
78
+ hub_data = config.LEARNING_HUBS[st.session_state.selected_hub]
79
+
80
+ st.markdown(f"""
81
+ <div class='success-box' style='text-align: center;'>
82
+ <h3 style='margin: 0;'>{hub_data['icon']} المحور المختار: {hub_data['name']}</h3>
83
+ <p style='margin: 0.5rem 0 0 0;'>{hub_data['description']}</p>
84
+ </div>
85
+ """, unsafe_allow_html=True)
86
+
87
+ if st.button("🔄 تغيير المحور"):
88
+ st.session_state.selected_hub = None
89
+ st.rerun()
90
+
91
+ st.markdown("---")
92
+
93
+ # الخطوة 2: توليد الخطة
94
+ st.markdown("### 📋 الخطوة 2: توليد خطتك المخصصة")
95
+
96
+ if "curriculum" not in st.session_state:
97
+ st.session_state.curriculum = None
98
+
99
+ if not st.session_state.curriculum:
100
+ st.markdown("""
101
+ <div class='info-box'>
102
+ <p style='margin: 0;'>
103
+ سنقوم الآن بتوليد منهج مخصص لك بناءً على:
104
+ </p>
105
+ <ul style='margin: 0.5rem 0 0 0;'>
106
+ <li>مستواك الحالي: <strong>{}</strong></li>
107
+ <li>الوقت المتاح: <strong>{} ساعة/أسبوع</strong></li>
108
+ <li>أسلوب التعلم: <strong>{}</strong></li>
109
+ <li>المحور: <strong>{}</strong></li>
110
+ </ul>
111
+ </div>
112
+ """.format(
113
+ profile.get("level", "مبتدئ"),
114
+ profile.get("available_hours", 10),
115
+ profile.get("learning_style", "مختلط"),
116
+ hub_data["name"]
117
+ ), unsafe_allow_html=True)
118
+
119
+ st.markdown("<br>", unsafe_allow_html=True)
120
+
121
+ col1, col2, col3 = st.columns([1, 2, 1])
122
+ with col2:
123
+ if st.button("✨ توليد الخطة المخصصة", use_container_width=True, type="primary"):
124
+ with st.spinner("🔄 جاري توليد خطتك المخصصة... قد يستغرق هذا دقيقة..."):
125
+ # محاولة توليد المنهج عبر API
126
+ curriculum_result = generate_curriculum(profile, st.session_state.selected_hub)
127
+
128
+ if curriculum_result and "curriculum" in curriculum_result:
129
+ st.session_state.curriculum = curriculum_result["curriculum"]
130
+ else:
131
+ # استخدام منهج افتراضي
132
+ default_result = generate_default_curriculum(st.session_state.selected_hub)
133
+ st.session_state.curriculum = default_result["curriculum"]
134
+
135
+ # توليد الجدول الزمني
136
+ st.session_state.schedule = generate_schedule(
137
+ st.session_state.curriculum,
138
+ profile.get("available_hours", 10)
139
+ )
140
+
141
+ st.session_state.completed_units = []
142
+ st.session_state.completed_lessons = []
143
+
144
+ st.rerun()
145
+
146
+ else:
147
+ # عرض الخطة المُولدة
148
+ show_success_message("✅ تم توليد خطتك المخصصة بنجاح!")
149
+
150
+ st.markdown("### 📊 نظرة عامة على خطتك")
151
+
152
+ curriculum = st.session_state.curriculum
153
+
154
+ col1, col2, col3, col4 = st.columns(4)
155
+ with col1:
156
+ st.metric("عدد الأسابيع", f"{len(curriculum)} أسبوع")
157
+ with col2:
158
+ total_lessons = sum(len(unit.get("lessons", [])) for unit in curriculum)
159
+ st.metric("إجمالي الدروس", total_lessons)
160
+ with col3:
161
+ total_hours = sum(unit.get("estimated_time_mins", 0) for unit in curriculum) / 60
162
+ st.metric("إجمالي الساعات", f"~{int(total_hours)} ساعة")
163
+ with col4:
164
+ st.metric("التجارب العملية", len(curriculum))
165
+
166
+ st.markdown("---")
167
+
168
+ # عرض الوحدات
169
+ st.markdown("### 📚 الوحدات التعليمية (12 أسبوع)")
170
+
171
+ for unit in curriculum[:6]: # عرض أول 6 وحدات فقط
172
+ week = unit.get("week", 0)
173
+ title = unit.get("unit_title", f"الوحدة {week}")
174
+ time_mins = unit.get("estimated_time_mins", 0)
175
+ time_hours = round(time_mins / 60, 1)
176
+
177
+ with st.expander(f"📖 الأسبوع {week}: {title}"):
178
+ st.markdown(f"**⏱️ الوقت المقدر:** {time_hours} ساعة ({time_mins} دقيقة)")
179
+
180
+ # الدروس
181
+ st.markdown("**📚 الدروس:**")
182
+ for lesson in unit.get("lessons", []):
183
+ st.markdown(f"- {lesson.get('title', '')} ({lesson.get('duration_mins', 0)} دقيقة) - {lesson.get('type', 'درس')}")
184
+
185
+ # التجربة
186
+ if unit.get("experiment"):
187
+ exp = unit["experiment"]
188
+ st.markdown("**🔬 التجربة العملية:**")
189
+ st.markdown(f"- **{exp.get('title', '')}**")
190
+ st.markdown(f" {exp.get('description', '')}")
191
+ st.markdown(f" *المخرج المطلوب:* {exp.get('deliverable', 'غير محدد')}")
192
+
193
+ if len(curriculum) > 6:
194
+ st.info(f"💡 هناك {len(curriculum) - 6} وحدات إضافية. ستظهر جميعها في صفحة الدروس.")
195
+
196
+ st.markdown("<br>", unsafe_allow_html=True)
197
+
198
+ # أزرار الإجراءات
199
+ col1, col2, col3 = st.columns(3)
200
+
201
+ with col1:
202
+ if st.button("🔄 إعادة التوليد", use_container_width=True):
203
+ st.session_state.curriculum = None
204
+ st.rerun()
205
+
206
+ with col2:
207
+ if st.button("📥 تنزيل الخطة (JSON)", use_container_width=True):
208
+ import json
209
+ curriculum_json = json.dumps({
210
+ "hub": st.session_state.selected_hub,
211
+ "profile": profile,
212
+ "curriculum": curriculum
213
+ }, ensure_ascii=False, indent=2)
214
+ st.download_button(
215
+ "تنزيل",
216
+ data=curriculum_json,
217
+ file_name="my_learning_plan.json",
218
+ mime="application/json"
219
+ )
220
+
221
+ with col3:
222
+ if st.button("📚 ابدأ التعلم!", use_container_width=True, type="primary"):
223
+ st.session_state.page = "lesson"
224
+ st.rerun()
225
+
226
+ # زر العودة
227
+ st.markdown("<br><br>", unsafe_allow_html=True)
228
+ if st.button("⬅️ العودة للملف التعريفي"):
229
+ st.session_state.page = "profile"
230
+ st.rerun()
231
+
232
+
233
+ if __name__ == "__main__":
234
+ show()
pages/profile.py ADDED
@@ -0,0 +1,253 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ صفحة الملف التعريفي - رفع أو إنشاء ملف تعريفي
3
+ """
4
+ import streamlit as st
5
+ import json
6
+ import sys
7
+ sys.path.append('..')
8
+ from utils.ui_utils import show_hero_section, show_success_message, show_warning_message, load_custom_css
9
+ from utils.data_utils import parse_profile_file, create_default_profile, validate_profile, export_profile_to_json
10
+
11
+
12
+ def show():
13
+ """عرض صفحة الملف التعريفي"""
14
+
15
+ load_custom_css()
16
+
17
+ st.markdown("## 👤 الملف التعريفي")
18
+ st.markdown("---")
19
+
20
+ # خيارات إنشاء الملف
21
+ st.markdown("### 🎯 كيف تريد إنشاء ملفك؟")
22
+
23
+ tab1, tab2, tab3 = st.tabs(["📝 تعبئة نموذج", "📤 رفع ملف", "👁️ معاينة الملف"])
24
+
25
+ # Tab 1: تعبئة نموذج
26
+ with tab1:
27
+ st.markdown("#### املأ المعلومات التالية:")
28
+
29
+ col1, col2 = st.columns(2)
30
+
31
+ with col1:
32
+ name = st.text_input("الاسم الكامل *", value=st.session_state.get("profile", {}).get("name", ""))
33
+ age = st.text_input("العمر", value=st.session_state.get("profile", {}).get("age", ""))
34
+ level = st.selectbox(
35
+ "المستوى الحالي *",
36
+ ["مبتدئ", "متوسط", "متقدم", "خبير"],
37
+ index=0
38
+ )
39
+
40
+ with col2:
41
+ available_hours = st.number_input(
42
+ "الوقت المتاح أسبوعياً (ساعات) *",
43
+ min_value=1,
44
+ max_value=40,
45
+ value=st.session_state.get("profile", {}).get("available_hours", 10)
46
+ )
47
+
48
+ learning_style = st.selectbox(
49
+ "أسلوب التعلم المفضل",
50
+ ["بصري (فيديوهات، صور)", "نصي (قراءة، مقالات)", "عملي (تطبيقات، مشاريع)", "مختلط (كل ما سبق)"],
51
+ index=3
52
+ )
53
+
54
+ st.markdown("---")
55
+
56
+ skills = st.text_area(
57
+ "المهارات الحالية (اختياري)",
58
+ placeholder="مثال: برمجة Python، تصميم جرافيك، تحليل بيانات...",
59
+ value=st.session_state.get("profile", {}).get("skills", "")
60
+ )
61
+
62
+ goals = st.text_area(
63
+ "أهدافك من التعلم *",
64
+ placeholder="مثال: أريد تعلم الذكاء الاصطناعي لبناء تطبيقات ذكية...",
65
+ value=st.session_state.get("profile", {}).get("goals", "")
66
+ )
67
+
68
+ st.markdown("<br>", unsafe_allow_html=True)
69
+
70
+ if st.button("💾 حفظ الملف التعريفي", use_container_width=True, type="primary"):
71
+ # إنشاء الملف
72
+ profile = {
73
+ "name": name,
74
+ "age": age,
75
+ "level": level,
76
+ "skills": [s.strip() for s in skills.split(",") if s.strip()] if skills else [],
77
+ "available_hours": available_hours,
78
+ "learning_style": learning_style,
79
+ "goals": goals,
80
+ "preferences": {
81
+ "language": "ar",
82
+ "notifications": True,
83
+ "theme": "light"
84
+ }
85
+ }
86
+
87
+ # التحقق من الصحة
88
+ is_valid, errors = validate_profile(profile)
89
+
90
+ if is_valid:
91
+ st.session_state.profile = profile
92
+ show_success_message("✅ تم حفظ ملفك التعريفي بنجاح!")
93
+ st.balloons()
94
+ st.markdown("<br>", unsafe_allow_html=True)
95
+
96
+ col1, col2 = st.columns(2)
97
+ with col1:
98
+ if st.button("📅 التالي: اختيار الخطة", use_container_width=True):
99
+ st.session_state.page = "plan"
100
+ st.rerun()
101
+
102
+ with col2:
103
+ # تنزيل الملف
104
+ json_str = export_profile_to_json(profile)
105
+ st.download_button(
106
+ "📥 تنزيل الملف (JSON)",
107
+ data=json_str,
108
+ file_name="my_profile.json",
109
+ mime="application/json",
110
+ use_container_width=True
111
+ )
112
+ else:
113
+ for error in errors:
114
+ show_warning_message(f"⚠️ {error}")
115
+
116
+ # Tab 2: رفع ملف
117
+ with tab2:
118
+ st.markdown("#### 📤 ارفع ملف تعريفي موجود")
119
+ st.markdown("يمكنك رفع ملف JSON أو TXT يحتوي على معلوماتك")
120
+
121
+ uploaded_file = st.file_uploader(
122
+ "اختر ملفاً",
123
+ type=["json", "txt", "md"],
124
+ help="ملف JSON، TXT، أو Markdown"
125
+ )
126
+
127
+ if uploaded_file:
128
+ try:
129
+ content = uploaded_file.read().decode("utf-8")
130
+ file_type = uploaded_file.name.split(".")[-1]
131
+
132
+ parsed_profile = parse_profile_file(content, file_type)
133
+
134
+ if parsed_profile:
135
+ show_success_message("تم تحليل الملف بنجاح!")
136
+
137
+ # عرض محتوى الملف
138
+ st.json(parsed_profile)
139
+
140
+ if st.button("✅ استخدام هذا الملف", use_container_width=True):
141
+ st.session_state.profile = parsed_profile
142
+ show_success_message("تم حفظ الملف التعريفي!")
143
+ st.balloons()
144
+ else:
145
+ show_warning_message("لم نتمكن من تحليل الملف. حاول مرة أخرى.")
146
+
147
+ except Exception as e:
148
+ show_warning_message(f"خطأ في قراءة الملف: {str(e)}")
149
+
150
+ # مثال على تنسيق الملف
151
+ with st.expander("💡 مثال على تنسيق الملف"):
152
+ st.markdown("**JSON Format:**")
153
+ st.code('''
154
+ {
155
+ "name": "أحمد محمد",
156
+ "age": "25",
157
+ "level": "متوسط",
158
+ "skills": ["Python", "تصميم"],
159
+ "available_hours": 15,
160
+ "learning_style": "مختلط",
161
+ "goals": "تعلم الذكاء الاصطناعي"
162
+ }
163
+ ''', language="json")
164
+
165
+ st.markdown("**Text Format:**")
166
+ st.code('''
167
+ الاسم: أحمد محمد
168
+ العمر: 25
169
+ المستوى: متوسط
170
+ المهارات: Python, تصميم
171
+ ساعات متاحة: 15
172
+ أسلوب التعلم: مختلط
173
+ الأهداف: تعلم الذكاء الاصطناعي
174
+ ''', language="text")
175
+
176
+ # Tab 3: معاينة الملف الحالي
177
+ with tab3:
178
+ if "profile" in st.session_state and st.session_state.profile:
179
+ st.markdown("#### 👁️ ملفك الحالي:")
180
+
181
+ profile = st.session_state.profile
182
+
183
+ # عرض جميل للملف
184
+ col1, col2 = st.columns(2)
185
+
186
+ with col1:
187
+ st.markdown(f"""
188
+ <div class='card'>
189
+ <h4>👤 المعلومات الشخصية</h4>
190
+ <p><strong>الاسم:</strong> {profile.get('name', 'غير محدد')}</p>
191
+ <p><strong>العمر:</strong> {profile.get('age', 'غير محدد')}</p>
192
+ <p><strong>المستوى:</strong> {profile.get('level', 'غير محدد')}</p>
193
+ </div>
194
+ """, unsafe_allow_html=True)
195
+
196
+ with col2:
197
+ st.markdown(f"""
198
+ <div class='card'>
199
+ <h4>⏰ الوقت والتفضيلات</h4>
200
+ <p><strong>الوقت المتاح:</strong> {profile.get('available_hours', 0)} ساعة/أسبوع</p>
201
+ <p><strong>أسلوب التعلم:</strong> {profile.get('learning_style', 'غير محدد')}</p>
202
+ </div>
203
+ """, unsafe_allow_html=True)
204
+
205
+ st.markdown(f"""
206
+ <div class='card'>
207
+ <h4>🎯 الأهداف</h4>
208
+ <p>{profile.get('goals', 'لم يتم تحديد أهداف')}</p>
209
+ </div>
210
+ """, unsafe_allow_html=True)
211
+
212
+ if profile.get('skills'):
213
+ st.markdown(f"""
214
+ <div class='card'>
215
+ <h4>💪 المهارات الحالية</h4>
216
+ <p>{', '.join(profile.get('skills', [])) if isinstance(profile.get('skills'), list) else profile.get('skills', 'لا توجد')}</p>
217
+ </div>
218
+ """, unsafe_allow_html=True)
219
+
220
+ st.markdown("<br>", unsafe_allow_html=True)
221
+
222
+ col1, col2, col3 = st.columns(3)
223
+
224
+ with col1:
225
+ if st.button("✏️ تعديل الملف", use_container_width=True):
226
+ st.info("عد إلى تبويب 'تعبئة نموذج' للتعديل")
227
+
228
+ with col2:
229
+ json_str = export_profile_to_json(profile)
230
+ st.download_button(
231
+ "📥 تنزيل",
232
+ data=json_str,
233
+ file_name="my_profile.json",
234
+ mime="application/json",
235
+ use_container_width=True
236
+ )
237
+
238
+ with col3:
239
+ if st.button("📅 التالي: الخطة", use_container_width=True, type="primary"):
240
+ st.session_state.page = "plan"
241
+ st.rerun()
242
+ else:
243
+ st.info("لم يتم إنشاء ملف تعريفي بعد. عد إلى تبويب 'تعبئة نموذج' أو 'رفع ملف'")
244
+
245
+ # زر العودة
246
+ st.markdown("<br><br>", unsafe_allow_html=True)
247
+ if st.button("⬅️ العودة للصفحة الرئيسية"):
248
+ st.session_state.page = "home"
249
+ st.rerun()
250
+
251
+
252
+ if __name__ == "__main__":
253
+ show()
pages/progress.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ صفحة التقدم والمحفظة - عرض الإنجازات والإحصائيات
3
+ """
4
+ import streamlit as st
5
+ import plotly.graph_objects as go
6
+ import plotly.express as px
7
+ import sys
8
+ sys.path.append('..')
9
+ from utils.ui_utils import load_custom_css, show_progress_bar, create_stat_card
10
+ from utils.data_utils import calculate_progress, get_completion_stats
11
+ import pandas as pd
12
+
13
+
14
+ def show():
15
+ """عرض صفحة التقدم والإنجازات"""
16
+
17
+ load_custom_css()
18
+
19
+ st.markdown("## 📊 تقدمك وإنجازاتك")
20
+ st.markdown("---")
21
+
22
+ # التحقق من وجود منهج
23
+ if "curriculum" not in st.session_state or not st.session_state.curriculum:
24
+ st.warning("⚠️ لم يتم إنشاء خطة تعليمية بعد!")
25
+ if st.button("📅 إنشاء خطة"):
26
+ st.session_state.page = "plan"
27
+ st.rerun()
28
+ return
29
+
30
+ curriculum = st.session_state.curriculum
31
+
32
+ # تهيئة البيانات
33
+ if "completed_lessons" not in st.session_state:
34
+ st.session_state.completed_lessons = []
35
+
36
+ if "completed_experiments" not in st.session_state:
37
+ st.session_state.completed_experiments = []
38
+
39
+ # حساب الإحصائيات
40
+ stats = get_completion_stats(st.session_state.completed_lessons, curriculum)
41
+
42
+ # نظرة عامة
43
+ st.markdown("### 🎯 نظرة عامة")
44
+
45
+ col1, col2, col3, col4 = st.columns(4)
46
+
47
+ with col1:
48
+ st.markdown(create_stat_card(
49
+ "الدروس المكتملة",
50
+ f"{stats['lessons']['completed']}/{stats['lessons']['total']}",
51
+ "📚"
52
+ ), unsafe_allow_html=True)
53
+
54
+ with col2:
55
+ st.markdown(create_stat_card(
56
+ "التجارب المكتملة",
57
+ f"{stats['experiments']['completed']}/{stats['experiments']['total']}",
58
+ "🔬"
59
+ ), unsafe_allow_html=True)
60
+
61
+ with col3:
62
+ st.markdown(create_stat_card(
63
+ "نسبة الإنجاز",
64
+ f"{stats['overall_percentage']:.1f}%",
65
+ "🎯"
66
+ ), unsafe_allow_html=True)
67
+
68
+ with col4:
69
+ total_weeks = len(curriculum)
70
+ completed_weeks = len([u for u in curriculum if all(
71
+ f"{u['week']}_{lesson['title']}" in st.session_state.completed_lessons
72
+ for lesson in u.get('lessons', [])
73
+ )])
74
+ st.markdown(create_stat_card(
75
+ "الأسابيع المكتملة",
76
+ f"{completed_weeks}/{total_weeks}",
77
+ "📅"
78
+ ), unsafe_allow_html=True)
79
+
80
+ st.markdown("---")
81
+
82
+ # شريط التقدم الإجمالي
83
+ st.markdown("### 📈 التقدم الإجمالي")
84
+ show_progress_bar(stats['overall_percentage'], get_progress_status(stats['overall_percentage']))
85
+
86
+ st.markdown("---")
87
+
88
+ # رسم بياني دائري
89
+ col1, col2 = st.columns(2)
90
+
91
+ with col1:
92
+ st.markdown("### 📚 توزيع الدروس")
93
+
94
+ fig = go.Figure(data=[go.Pie(
95
+ labels=['مكتملة', 'متبقية'],
96
+ values=[stats['lessons']['completed'], stats['lessons']['total'] - stats['lessons']['completed']],
97
+ hole=0.6,
98
+ marker_colors=['#48bb78', '#e2e8f0']
99
+ )])
100
+
101
+ fig.update_layout(
102
+ showlegend=True,
103
+ height=300,
104
+ margin=dict(l=20, r=20, t=20, b=20),
105
+ annotations=[dict(
106
+ text=f"{stats['lessons']['percentage']:.1f}%",
107
+ x=0.5, y=0.5,
108
+ font_size=28,
109
+ font_color='#667eea',
110
+ showarrow=False
111
+ )]
112
+ )
113
+
114
+ st.plotly_chart(fig, use_container_width=True)
115
+
116
+ with col2:
117
+ st.markdown("### 🔬 توزيع التجارب")
118
+
119
+ fig2 = go.Figure(data=[go.Pie(
120
+ labels=['مكتملة', 'متبقية'],
121
+ values=[stats['experiments']['completed'], stats['experiments']['total'] - stats['experiments']['completed']],
122
+ hole=0.6,
123
+ marker_colors=['#764ba2', '#e2e8f0']
124
+ )])
125
+
126
+ fig2.update_layout(
127
+ showlegend=True,
128
+ height=300,
129
+ margin=dict(l=20, r=20, t=20, b=20),
130
+ annotations=[dict(
131
+ text=f"{stats['experiments']['percentage']:.1f}%",
132
+ x=0.5, y=0.5,
133
+ font_size=28,
134
+ font_color='#764ba2',
135
+ showarrow=False
136
+ )]
137
+ )
138
+
139
+ st.plotly_chart(fig2, use_container_width=True)
140
+
141
+ st.markdown("---")
142
+
143
+ # التقدم الأسبوعي
144
+ st.markdown("### 📅 التقدم الأسبوعي")
145
+
146
+ weekly_progress = []
147
+ for unit in curriculum:
148
+ week = unit['week']
149
+ unit_lessons = unit.get('lessons', [])
150
+ completed_in_week = sum(1 for lesson in unit_lessons if f"{week}_{lesson['title']}" in st.session_state.completed_lessons)
151
+ total_in_week = len(unit_lessons)
152
+ percentage = (completed_in_week / total_in_week * 100) if total_in_week > 0 else 0
153
+
154
+ weekly_progress.append({
155
+ 'الأسبوع': f"أسبوع {week}",
156
+ 'النسبة': percentage,
157
+ 'مكتمل': completed_in_week,
158
+ 'الإجمالي': total_in_week
159
+ })
160
+
161
+ df = pd.DataFrame(weekly_progress)
162
+
163
+ fig3 = px.bar(
164
+ df,
165
+ x='الأسبوع',
166
+ y='النسبة',
167
+ text='النسبة',
168
+ color='النسبة',
169
+ color_continuous_scale=['#e2e8f0', '#667eea', '#764ba2'],
170
+ range_color=[0, 100]
171
+ )
172
+
173
+ fig3.update_traces(texttemplate='%{text:.1f}%', textposition='outside')
174
+ fig3.update_layout(
175
+ height=400,
176
+ showlegend=False,
177
+ xaxis_title="",
178
+ yaxis_title="نسبة الإنجاز (%)",
179
+ yaxis_range=[0, 110]
180
+ )
181
+
182
+ st.plotly_chart(fig3, use_container_width=True)
183
+
184
+ st.markdown("---")
185
+
186
+ # الشارات والإنجازات
187
+ st.markdown("### 🏆 الشارات والإنجازات")
188
+
189
+ badges = []
190
+
191
+ # شارة البداية
192
+ if stats['lessons']['completed'] > 0:
193
+ badges.append({"name": "🌱 الخطوة الأولى", "desc": "أكملت أول درس"})
194
+
195
+ # شارة الأسبوع الأول
196
+ if completed_weeks >= 1:
197
+ badges.append({"name": "🎯 أسبوع كامل", "desc": "أكملت أسبوعاً كاملاً"})
198
+
199
+ # شارة نصف الطريق
200
+ if stats['overall_percentage'] >= 50:
201
+ badges.append({"name": "⭐ منتصف الرحلة", "desc": "أكملت نصف المنهج"})
202
+
203
+ # شارة التجارب
204
+ if stats['experiments']['completed'] >= 3:
205
+ badges.append({"name": "🔬 خبير تجارب", "desc": "أكملت 3 تجارب عملية"})
206
+
207
+ # شارة المثابرة
208
+ if stats['lessons']['completed'] >= 20:
209
+ badges.append({"name": "💪 مثابر", "desc": "أكملت 20 درساً"})
210
+
211
+ # شارة الإنجاز الكامل
212
+ if stats['overall_percentage'] >= 100:
213
+ badges.append({"name": "🎉 متخرج!", "desc": "أكملت المنهج بالكامل"})
214
+
215
+ if badges:
216
+ cols = st.columns(min(len(badges), 4))
217
+ for idx, badge in enumerate(badges):
218
+ col = cols[idx % 4]
219
+ with col:
220
+ st.markdown(f"""
221
+ <div class='card' style='text-align: center; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; min-height: 150px; display: flex; flex-direction: column; justify-content: center;'>
222
+ <h2 style='margin: 0.5rem 0; color: white;'>{badge['name'].split()[0]}</h2>
223
+ <h4 style='margin: 0.25rem 0; color: white;'>{' '.join(badge['name'].split()[1:])}</h4>
224
+ <p style='margin: 0.5rem 0 0 0; font-size: 0.85rem; color: rgba(255,255,255,0.9);'>{badge['desc']}</p>
225
+ </div>
226
+ """, unsafe_allow_html=True)
227
+ else:
228
+ st.info("ابدأ التعلم لتحصل على شاراتك الأولى! 🎯")
229
+
230
+ st.markdown("---")
231
+
232
+ # المحفظة
233
+ st.markdown("### 📁 محفظتك")
234
+
235
+ st.markdown("""
236
+ <div class='info-box'>
237
+ <strong>💡 محفظتك تحتوي على:</strong>
238
+ <ul style='margin: 0.5rem 0 0 0;'>
239
+ <li>جميع الدروس المكتملة مع ملاحظاتك</li>
240
+ <li>التجارب العملية التي أنجزتها</li>
241
+ <li>المشاريع الجماعية التي شاركت فيها</li>
242
+ <li>الشارات والإنجازات</li>
243
+ </ul>
244
+ </div>
245
+ """, unsafe_allow_html=True)
246
+
247
+ # زر تصدير المحفظة
248
+ if st.button("📥 تصدير المحفظة (JSON)", use_container_width=True):
249
+ import json
250
+ from datetime import datetime
251
+
252
+ portfolio = {
253
+ "profile": st.session_state.get('profile', {}),
254
+ "curriculum": curriculum,
255
+ "stats": stats,
256
+ "completed_lessons": st.session_state.completed_lessons,
257
+ "completed_experiments": st.session_state.completed_experiments,
258
+ "badges": badges,
259
+ "exported_at": datetime.now().strftime("%Y-%m-%d %H:%M")
260
+ }
261
+
262
+ portfolio_json = json.dumps(portfolio, ensure_ascii=False, indent=2)
263
+ st.download_button(
264
+ "💾 تنزيل المحفظة",
265
+ data=portfolio_json,
266
+ file_name="my_portfolio.json",
267
+ mime="application/json"
268
+ )
269
+
270
+ # أزرار التنقل
271
+ st.markdown("<br><br>", unsafe_allow_html=True)
272
+ st.markdown("---")
273
+
274
+ col1, col2, col3 = st.columns(3)
275
+
276
+ with col1:
277
+ if st.button("📚 العودة للدروس", use_container_width=True):
278
+ st.session_state.page = "lesson"
279
+ st.rerun()
280
+
281
+ with col2:
282
+ if st.button("🔬 التجارب", use_container_width=True):
283
+ st.session_state.page = "experiment"
284
+ st.rerun()
285
+
286
+ with col3:
287
+ if st.button("👥 المشاريع", use_container_width=True):
288
+ st.session_state.page = "projects"
289
+ st.rerun()
290
+
291
+
292
+ def get_progress_status(percentage: float) -> str:
293
+ """تحديد حالة التقدم"""
294
+ if percentage < 25:
295
+ return "بداية الرحلة 🌱"
296
+ elif percentage < 50:
297
+ return "في الطريق 🚀"
298
+ elif percentage < 75:
299
+ return "تقدم ممتاز 🌟"
300
+ elif percentage < 100:
301
+ return "قرب الإنجاز 🎯"
302
+ else:
303
+ return "مكتمل! 🎉"
304
+
305
+
306
+ if __name__ == "__main__":
307
+ show()
pages/projects.py ADDED
@@ -0,0 +1,257 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ صفحة المشاريع الجماعية - إنشاء وإدارة المشاريع
3
+ """
4
+ import streamlit as st
5
+ import sys
6
+ sys.path.append('..')
7
+ from utils.ui_utils import load_custom_css, show_success_message, show_warning_message
8
+ from datetime import datetime
9
+
10
+
11
+ def show():
12
+ """عرض صفحة المشاريع الجماعية"""
13
+
14
+ load_custom_css()
15
+
16
+ st.markdown("## 👥 المشاريع الجماعية")
17
+ st.markdown("---")
18
+
19
+ # تهيئة المشاريع
20
+ if "projects" not in st.session_state:
21
+ st.session_state.projects = []
22
+
23
+ if "user_projects" not in st.session_state:
24
+ st.session_state.user_projects = [] # المشاريع التي انضم لها المستخدم
25
+
26
+ # علامات تبويب
27
+ tab1, tab2, tab3 = st.tabs(["📋 المشاريع المتاحة", "➕ إنشاء مشروع", "📁 مشاريعي"])
28
+
29
+ # Tab 1: المشاريع المتاحة
30
+ with tab1:
31
+ st.markdown("### 🌟 المشاريع المتاحة للانضمام")
32
+
33
+ if not st.session_state.projects:
34
+ st.info("""
35
+ 🎯 **لا توجد مشاريع متاحة حالياً**
36
+
37
+ يمكنك إنشاء مشروع جديد من تبويب "إنشاء مشروع" وسيظهر هنا للآخرين للانضمام إليه!
38
+ """)
39
+ else:
40
+ for project in st.session_state.projects:
41
+ if project['id'] not in st.session_state.user_projects:
42
+ with st.expander(f"🚀 {project['title']}"):
43
+ col1, col2 = st.columns([3, 1])
44
+
45
+ with col1:
46
+ st.markdown(f"""
47
+ <div class='card'>
48
+ <p><strong>الوصف:</strong> {project['description']}</p>
49
+ <p><strong>👤 منشئ المشروع:</strong> {project['owner']}</p>
50
+ <p><strong>👥 الأعضاء:</strong> {len(project['members'])}/{project['max_members']}</p>
51
+ <p><strong>📅 تاريخ الإنشاء:</strong> {project['created_at']}</p>
52
+ <p><strong>🏷️ المهارات المطلوبة:</strong> {', '.join(project['skills'])}</p>
53
+ </div>
54
+ """, unsafe_allow_html=True)
55
+
56
+ with col2:
57
+ if len(project['members']) < project['max_members']:
58
+ if st.button(f"✅ انضم", key=f"join_{project['id']}", use_container_width=True):
59
+ # إضافة المستخدم للمشروع
60
+ user_name = st.session_state.get('profile', {}).get('name', 'مستخدم')
61
+ project['members'].append(user_name)
62
+ st.session_state.user_projects.append(project['id'])
63
+ show_success_message(f"تم انضمامك لمشروع '{project['title']}'!")
64
+ st.balloons()
65
+ st.rerun()
66
+ else:
67
+ st.warning("مكتمل")
68
+
69
+ # Tab 2: إنشاء مشروع
70
+ with tab2:
71
+ st.markdown("### ➕ إنشاء مشروع جديد")
72
+
73
+ st.markdown("""
74
+ <div class='info-box'>
75
+ <strong>💡 نصائح لمشروع ناجح:</strong>
76
+ <ul style='margin: 0.5rem 0 0 0;'>
77
+ <li>اختر عنواناً واضحاً ووصفاً مفصلاً</li>
78
+ <li>حدد المهارات المطلوبة بدقة</li>
79
+ <li>ضع خطة زمنية واقعية</li>
80
+ <li>كن نشطاً في التواصل مع الفريق</li>
81
+ </ul>
82
+ </div>
83
+ """, unsafe_allow_html=True)
84
+
85
+ st.markdown("<br>", unsafe_allow_html=True)
86
+
87
+ with st.form("create_project_form"):
88
+ project_title = st.text_input("عنوان المشروع *", placeholder="مثال: تطبيق ذكاء اصطناعي للتعرف على الصور")
89
+
90
+ project_description = st.text_area(
91
+ "وصف المشروع *",
92
+ placeholder="اشرح فكرة المشروع، الأهداف، والمخرجات المتوقعة...",
93
+ height=150
94
+ )
95
+
96
+ col1, col2 = st.columns(2)
97
+ with col1:
98
+ max_members = st.number_input("الحد الأقصى للأعضاء", min_value=2, max_value=10, value=4)
99
+
100
+ with col2:
101
+ duration_weeks = st.number_input("المدة (بالأسابيع)", min_value=1, max_value=12, value=4)
102
+
103
+ skills_input = st.text_input(
104
+ "المهارات المطلوبة (افصل بفواصل)",
105
+ placeholder="Python, تصميم, تحليل بيانات"
106
+ )
107
+
108
+ submitted = st.form_submit_button("🚀 إنشاء المشروع", use_container_width=True, type="primary")
109
+
110
+ if submitted:
111
+ if project_title.strip() and project_description.strip():
112
+ # إنشاء المشروع
113
+ new_project = {
114
+ "id": f"proj_{len(st.session_state.projects) + 1}_{datetime.now().strftime('%Y%m%d%H%M%S')}",
115
+ "title": project_title,
116
+ "description": project_description,
117
+ "owner": st.session_state.get('profile', {}).get('name', 'مستخدم'),
118
+ "members": [st.session_state.get('profile', {}).get('name', 'مستخدم')],
119
+ "max_members": max_members,
120
+ "skills": [s.strip() for s in skills_input.split(",") if s.strip()],
121
+ "duration_weeks": duration_weeks,
122
+ "created_at": datetime.now().strftime("%Y-%m-%d"),
123
+ "status": "active",
124
+ "tasks": [],
125
+ "deliverables": []
126
+ }
127
+
128
+ st.session_state.projects.append(new_project)
129
+ st.session_state.user_projects.append(new_project['id'])
130
+
131
+ show_success_message(f"✅ تم إنشاء مشروع '{project_title}' بنجاح!")
132
+ st.balloons()
133
+ st.rerun()
134
+ else:
135
+ show_warning_message("⚠️ يجب ملء جميع الحقول المطلوبة")
136
+
137
+ # Tab 3: مشاريعي
138
+ with tab3:
139
+ st.markdown("### 📁 المشاريع التي أشارك فيها")
140
+
141
+ my_projects = [p for p in st.session_state.projects if p['id'] in st.session_state.user_projects]
142
+
143
+ if not my_projects:
144
+ st.info("لم تنضم لأي مشروع بعد! تصفح المشاريع المتاحة أو أنشئ مشروعاً جديداً.")
145
+ else:
146
+ for project in my_projects:
147
+ user_name = st.session_state.get('profile', {}).get('name', 'مستخدم')
148
+ is_owner = project['owner'] == user_name
149
+
150
+ with st.expander(f"{'👑' if is_owner else '👤'} {project['title']}", expanded=True):
151
+
152
+ # معلومات المشروع
153
+ st.markdown(f"""
154
+ <div class='card'>
155
+ <p><strong>الوصف:</strong> {project['description']}</p>
156
+ <p><strong>👑 منشئ المشروع:</strong> {project['owner']}</p>
157
+ <p><strong>⏱️ المدة:</strong> {project['duration_weeks']} أسابيع</p>
158
+ <p><strong>📅 تاريخ البدء:</strong> {project['created_at']}</p>
159
+ </div>
160
+ """, unsafe_allow_html=True)
161
+
162
+ # الأعضاء
163
+ st.markdown("**👥 أعضاء الفريق:**")
164
+ members_display = ", ".join([f"{'👑 ' if m == project['owner'] else ''}{m}" for m in project['members']])
165
+ st.markdown(f"<div class='badge badge-info'>{members_display}</div>", unsafe_allow_html=True)
166
+
167
+ st.markdown("---")
168
+
169
+ # المهام
170
+ st.markdown("**📋 المهام:**")
171
+
172
+ project_tasks_key = f"tasks_{project['id']}"
173
+ if project_tasks_key not in st.session_state:
174
+ st.session_state[project_tasks_key] = project.get('tasks', [])
175
+
176
+ tasks = st.session_state[project_tasks_key]
177
+
178
+ if tasks:
179
+ for idx, task in enumerate(tasks):
180
+ col1, col2 = st.columns([4, 1])
181
+ with col1:
182
+ st.markdown(f"- {task['title']} (المسؤول: {task['assigned_to']})")
183
+ with col2:
184
+ status_badge = "success" if task['status'] == 'completed' else "warning"
185
+ st.markdown(f"<span class='badge badge-{status_badge}'>{task['status']}</span>", unsafe_allow_html=True)
186
+ else:
187
+ st.info("لا توجد مهام بعد")
188
+
189
+ # إضافة مهمة جديدة (للمنشئ فقط)
190
+ if is_owner:
191
+ with st.form(f"add_task_{project['id']}"):
192
+ st.markdown("**➕ إضافة مهمة جديدة:**")
193
+ col1, col2 = st.columns(2)
194
+ with col1:
195
+ task_title = st.text_input("عنوان المهمة", key=f"task_title_{project['id']}")
196
+ with col2:
197
+ assigned_to = st.selectbox("تعيين إلى", project['members'], key=f"assign_{project['id']}")
198
+
199
+ if st.form_submit_button("➕ إضافة"):
200
+ if task_title.strip():
201
+ new_task = {
202
+ "title": task_title,
203
+ "assigned_to": assigned_to,
204
+ "status": "pending",
205
+ "created_at": datetime.now().strftime("%Y-%m-%d")
206
+ }
207
+ tasks.append(new_task)
208
+ project['tasks'] = tasks
209
+ show_success_message("تم إضافة المهمة!")
210
+ st.rerun()
211
+
212
+ st.markdown("---")
213
+
214
+ # منطقة التواصل
215
+ st.markdown("**💬 التواصل:**")
216
+ st.info("""
217
+ 💡 **نصيحة:** استخدم أدوات التواصل الخارجية للتنسيق:
218
+ - Slack / Discord للمحادثات
219
+ - GitHub للكود
220
+ - Google Drive / Notion للوثائق
221
+ """)
222
+
223
+ # رفع مخرجات
224
+ st.markdown("**📤 رفع مخرجات:**")
225
+ deliverable_file = st.file_uploader(
226
+ "ارفع ملفات المشروع",
227
+ key=f"upload_{project['id']}",
228
+ accept_multiple_files=True
229
+ )
230
+
231
+ if deliverable_file:
232
+ if st.button(f"💾 حفظ المخرجات", key=f"save_del_{project['id']}"):
233
+ for file in deliverable_file:
234
+ project['deliverables'].append({
235
+ "file_name": file.name,
236
+ "uploaded_by": user_name,
237
+ "uploaded_at": datetime.now().strftime("%Y-%m-%d %H:%M")
238
+ })
239
+ show_success_message("تم رفع المخرجات!")
240
+ st.rerun()
241
+
242
+ # عرض المخرجات
243
+ if project.get('deliverables'):
244
+ st.markdown("**📦 المخرجات المرفوعة:**")
245
+ for del_item in project['deliverables']:
246
+ st.markdown(f"- 📄 {del_item['file_name']} (بواسطة {del_item['uploaded_by']} - {del_item['uploaded_at']})")
247
+
248
+ # زر العودة
249
+ st.markdown("<br><br>", unsafe_allow_html=True)
250
+ st.markdown("---")
251
+ if st.button("⬅️ العودة للدروس"):
252
+ st.session_state.page = "lesson"
253
+ st.rerun()
254
+
255
+
256
+ if __name__ == "__main__":
257
+ show()
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit==1.32.0
2
+ streamlit-option-menu==0.3.12
3
+ streamlit-lottie==0.0.5
4
+ supabase==2.4.0
5
+ python-dotenv==1.0.1
6
+ requests==2.31.0
7
+ plotly==5.19.0
8
+ pandas==2.2.1
9
+ Pillow==10.2.0
10
+ python-multipart==0.0.9
tests/test_app.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ اختبارات بسيطة للتطبيق
3
+ """
4
+ import pytest
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ # إضافة المسار
9
+ sys.path.append(str(Path(__file__).parent.parent))
10
+
11
+ from utils.data_utils import validate_profile, create_default_profile, parse_profile_file
12
+ from utils.api_utils import generate_default_curriculum
13
+ import config
14
+
15
+
16
+ def test_create_default_profile():
17
+ """اختبار إنشاء ملف تعريفي افتراضي"""
18
+ profile = create_default_profile()
19
+
20
+ assert profile is not None
21
+ assert "name" in profile
22
+ assert "level" in profile
23
+ assert "available_hours" in profile
24
+ assert profile["available_hours"] == 10
25
+
26
+
27
+ def test_validate_profile_success():
28
+ """اختبار التحقق من ملف تعريفي صحيح"""
29
+ profile = {
30
+ "name": "أحمد",
31
+ "level": "متوسط",
32
+ "available_hours": 15
33
+ }
34
+
35
+ is_valid, errors = validate_profile(profile)
36
+
37
+ assert is_valid == True
38
+ assert len(errors) == 0
39
+
40
+
41
+ def test_validate_profile_missing_fields():
42
+ """اختبار التحقق من ملف تعريفي ناقص"""
43
+ profile = {
44
+ "name": "أحمد"
45
+ }
46
+
47
+ is_valid, errors = validate_profile(profile)
48
+
49
+ assert is_valid == False
50
+ assert len(errors) > 0
51
+
52
+
53
+ def test_validate_profile_invalid_hours():
54
+ """اختبار التحقق من ساعات غير صحيحة"""
55
+ profile = {
56
+ "name": "أحمد",
57
+ "level": "متوسط",
58
+ "available_hours": 50 # أكثر من الحد الأقصى
59
+ }
60
+
61
+ is_valid, errors = validate_profile(profile)
62
+
63
+ assert is_valid == False
64
+
65
+
66
+ def test_parse_json_profile():
67
+ """اختبار تحليل ملف JSON"""
68
+ json_content = '{"name": "سارة", "level": "مبتدئ", "available_hours": 10}'
69
+
70
+ profile = parse_profile_file(json_content, "json")
71
+
72
+ assert profile is not None
73
+ assert profile["name"] == "سارة"
74
+ assert profile["level"] == "مبتدئ"
75
+
76
+
77
+ def test_generate_default_curriculum():
78
+ """اختبار توليد منهج افتراضي"""
79
+ curriculum_result = generate_default_curriculum("ai_intro")
80
+
81
+ assert curriculum_result is not None
82
+ assert "curriculum" in curriculum_result
83
+ assert len(curriculum_result["curriculum"]) == 12 # 12 أسبوع
84
+
85
+
86
+ def test_learning_hubs_config():
87
+ """اختبار إعدادات المحاور"""
88
+ assert len(config.LEARNING_HUBS) > 0
89
+
90
+ for hub_id, hub_data in config.LEARNING_HUBS.items():
91
+ assert "name" in hub_data
92
+ assert "description" in hub_data
93
+ assert "icon" in hub_data
94
+ assert "duration_weeks" in hub_data
95
+ assert hub_data["duration_weeks"] == 12
96
+
97
+
98
+ if __name__ == "__main__":
99
+ pytest.main([__file__, "-v"])
utils/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ # ملف لجعل utils مجلداً قابلاً للاستيراد
utils/api_utils.py ADDED
@@ -0,0 +1,248 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ وظائف مساعدة للتعامل مع APIs (Hugging Face و Supabase)
3
+ """
4
+ import requests
5
+ import json
6
+ from typing import Dict, List, Optional
7
+ import config
8
+
9
+ def call_hf_api(prompt: str, max_tokens: int = 500, temperature: float = 0.7) -> Optional[str]:
10
+ """
11
+ استدعاء Hugging Face Inference API
12
+
13
+ Args:
14
+ prompt: النص المُدخل للنموذج
15
+ max_tokens: الحد الأقصى للـ tokens في الرد
16
+ temperature: درجة العشوائية (0-1)
17
+
18
+ Returns:
19
+ النص المُولّد أو None في حالة الخطأ
20
+ """
21
+ if not config.HF_API_TOKEN:
22
+ return None
23
+
24
+ headers = {
25
+ "Authorization": f"Bearer {config.HF_API_TOKEN}",
26
+ "Content-Type": "application/json"
27
+ }
28
+
29
+ payload = {
30
+ "inputs": prompt,
31
+ "parameters": {
32
+ "max_new_tokens": max_tokens,
33
+ "temperature": temperature,
34
+ "top_p": 0.9,
35
+ "return_full_text": False
36
+ }
37
+ }
38
+
39
+ try:
40
+ response = requests.post(
41
+ config.HF_API_URL,
42
+ headers=headers,
43
+ json=payload,
44
+ timeout=30
45
+ )
46
+
47
+ if response.status_code == 200:
48
+ result = response.json()
49
+ if isinstance(result, list) and len(result) > 0:
50
+ return result[0].get("generated_text", "")
51
+ return result.get("generated_text", "")
52
+ else:
53
+ print(f"خطأ في API: {response.status_code} - {response.text}")
54
+ return None
55
+
56
+ except Exception as e:
57
+ print(f"خطأ في استدعاء HF API: {str(e)}")
58
+ return None
59
+
60
+
61
+ def generate_curriculum(user_profile: Dict, hub_id: str) -> Optional[Dict]:
62
+ """
63
+ توليد منهج مخصص بناءً على ملف المستخدم
64
+
65
+ Args:
66
+ user_profile: بيانات المستخدم
67
+ hub_id: معرف المحور المختار
68
+
69
+ Returns:
70
+ المنهج المُولّد أو None
71
+ """
72
+ hub = config.LEARNING_HUBS.get(hub_id, {})
73
+
74
+ prompt = f"""أنت مصمم مناهج تعليمية خبير. مهمتك توليد خطة تعلم لمدة {config.PLAN_DURATION_WEEKS} أسبوعاً.
75
+
76
+ معلومات المستخدم:
77
+ - الاسم: {user_profile.get('name', 'غير محدد')}
78
+ - المستوى: {user_profile.get('level', 'مبتدئ')}
79
+ - الوقت المتاح أسبوعياً: {user_profile.get('available_hours', 10)} ساعات
80
+ - أسلوب التعلم المفضل: {user_profile.get('learning_style', 'مختلط')}
81
+
82
+ المحور المطلوب: {hub.get('name', 'غير محدد')}
83
+
84
+ ولّد خطة تعلم بصيغة JSON تحتوي على 12 وحدة أسبوعية. كل وحدة يجب أن تحتوي على:
85
+ - week: رقم الأسبوع
86
+ - unit_title: عنوان الوحدة
87
+ - estimated_time_mins: الوقت المقدر بالدقائق
88
+ - lessons: قائمة بالدروس (كل درس له: title, duration_mins, type, description)
89
+ - experiment: تجربة عملية (title, description, deliverable)
90
+
91
+ مثال على البنية:
92
+ {{
93
+ "curriculum": [
94
+ {{
95
+ "week": 1,
96
+ "unit_title": "...",
97
+ "estimated_time_mins": 600,
98
+ "lessons": [...],
99
+ "experiment": {{...}}
100
+ }}
101
+ ]
102
+ }}
103
+
104
+ ردّ فقط بصيغة JSON بدون أي نص إضافي."""
105
+
106
+ # محاولة استدعاء API
107
+ response = call_hf_api(prompt, max_tokens=2000, temperature=0.7)
108
+
109
+ if response:
110
+ try:
111
+ # محاولة استخراج JSON من الرد
112
+ json_start = response.find("{")
113
+ json_end = response.rfind("}") + 1
114
+ if json_start != -1 and json_end > json_start:
115
+ json_str = response[json_start:json_end]
116
+ return json.loads(json_str)
117
+ except:
118
+ pass
119
+
120
+ # في حالة الفشل، إرجاع منهج افتراضي
121
+ return generate_default_curriculum(hub_id)
122
+
123
+
124
+ def generate_default_curriculum(hub_id: str) -> Dict:
125
+ """
126
+ توليد منهج افتراضي في حالة عدم توفر API
127
+ """
128
+ hub = config.LEARNING_HUBS.get(hub_id, config.LEARNING_HUBS["ai_intro"])
129
+
130
+ curriculum = []
131
+ for week in range(1, 13):
132
+ unit = {
133
+ "week": week,
134
+ "unit_title": f"الوحدة {week}: {hub['name']} - جزء {week}",
135
+ "estimated_time_mins": 480,
136
+ "lessons": [
137
+ {
138
+ "title": f"درس {week}.1: المفاهيم الأساسية",
139
+ "duration_mins": 120,
140
+ "type": "concept",
141
+ "description": f"شرح المفاهيم الأساسية في الأسبوع {week}"
142
+ },
143
+ {
144
+ "title": f"درس {week}.2: التطبيق العملي",
145
+ "duration_mins": 180,
146
+ "type": "practical",
147
+ "description": f"تطبيق عملي على ما تم تعلمه"
148
+ }
149
+ ],
150
+ "experiment": {
151
+ "title": f"تجربة الأسبوع {week}",
152
+ "description": f"مشروع تطبيقي لتعزيز الفهم",
153
+ "deliverable": "ملف أو تقرير"
154
+ }
155
+ }
156
+ curriculum.append(unit)
157
+
158
+ return {"curriculum": curriculum}
159
+
160
+
161
+ def generate_lesson_content(lesson_title: str, user_context: Dict) -> str:
162
+ """
163
+ توليد محتوى درس مفصل
164
+ """
165
+ prompt = f"""أنت مدرس متخصص. اكتب درساً تعليمياً قصيراً (5-10 دقائق قراءة) عن:
166
+
167
+ {lesson_title}
168
+
169
+ الدرس يجب أن يتضمن:
170
+ 1. الفكرة المركزية (ملخص في سطرين)
171
+ 2. شرح مبسط مع أمثلة واقعية
172
+ 3. نقاط رئيسية (3-5 نقاط)
173
+ 4. سؤال للتفكير
174
+ 5. مصادر للمتابعة (اختياري)
175
+
176
+ اكتب بأسلوب واضح ومشجع."""
177
+
178
+ response = call_hf_api(prompt, max_tokens=800)
179
+
180
+ if response:
181
+ return response
182
+
183
+ # محتوى افتراضي
184
+ return f"""# {lesson_title}
185
+
186
+ ## الفكرة المركزية 💡
187
+ هذا درس تعليمي يهدف إلى تعريفك بالمفهوم الأساسي وتطبيقاته العملية.
188
+
189
+ ## الشرح 📖
190
+ سنستكشف معاً هذا الموضوع المهم من خلال أمثلة واقعية وتطبيقات عملية تساعدك على الفهم العميق.
191
+
192
+ ## النقاط الرئيسية ⭐
193
+ 1. المفهوم الأساسي وأهميته
194
+ 2. التطبيقات العملية في الحياة اليومية
195
+ 3. خطوات التنفيذ والممارسة
196
+ 4. الأخطاء الشائعة وكيفية تجنبها
197
+ 5. الخطوات التالية للتعمق
198
+
199
+ ## سؤال للتفكير 🤔
200
+ كيف يمكنك تطبيق ما تعلمته اليوم في مشروعك الشخصي أو عملك؟
201
+
202
+ ## للمتابعة 📚
203
+ - ابحث عن أمثلة إضافية في مجالك
204
+ - مارس التطبيق العملي بشكل يومي
205
+ - شارك ما تعلمته مع الآخرين
206
+ """
207
+
208
+
209
+ # دوال Supabase (اختيارية - يمكن استبدالها بملفات JSON محلية)
210
+ def init_supabase():
211
+ """تهيئة اتصال Supabase"""
212
+ if not config.USE_SUPABASE:
213
+ return None
214
+
215
+ try:
216
+ from supabase import create_client
217
+ return create_client(config.SUPABASE_URL, config.SUPABASE_KEY)
218
+ except:
219
+ return None
220
+
221
+
222
+ def save_profile_to_supabase(profile: Dict) -> bool:
223
+ """حفظ ملف تعريفي في Supabase"""
224
+ client = init_supabase()
225
+ if not client:
226
+ return False
227
+
228
+ try:
229
+ client.table("profiles").insert(profile).execute()
230
+ return True
231
+ except:
232
+ return False
233
+
234
+
235
+ def get_profile_from_supabase(user_id: str) -> Optional[Dict]:
236
+ """جلب ملف تعريفي من Supabase"""
237
+ client = init_supabase()
238
+ if not client:
239
+ return None
240
+
241
+ try:
242
+ response = client.table("profiles").select("*").eq("user_id", user_id).execute()
243
+ if response.data:
244
+ return response.data[0]
245
+ except:
246
+ pass
247
+
248
+ return None
utils/data_utils.py ADDED
@@ -0,0 +1,266 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ وظائف معالجة البيانات (تحليل الملفات التعريفية، إدارة التقدم، إلخ)
3
+ """
4
+ import json
5
+ from typing import Dict, List, Optional
6
+ from datetime import datetime, timedelta
7
+ import io
8
+
9
+
10
+ def parse_profile_file(file_content: str, file_type: str = "json") -> Optional[Dict]:
11
+ """
12
+ تحليل ملف تعريفي مرفوع
13
+
14
+ Args:
15
+ file_content: محتوى الملف
16
+ file_type: نوع الملف (json, txt, markdown)
17
+
18
+ Returns:
19
+ القاموس المُحلل أو None
20
+ """
21
+ try:
22
+ if file_type == "json":
23
+ return json.loads(file_content)
24
+
25
+ elif file_type in ["txt", "markdown", "md"]:
26
+ # تحليل بسيط من نص حر
27
+ profile = {
28
+ "name": extract_field(file_content, ["name", "الاسم", "اسم"]),
29
+ "age": extract_field(file_content, ["age", "العمر", "عمر"]),
30
+ "level": extract_field(file_content, ["level", "المستوى", "مستوى"]),
31
+ "skills": extract_field(file_content, ["skills", "المهارات", "مهارات"]),
32
+ "available_hours": extract_field(file_content, ["hours", "ساعات", "وقت متاح"]),
33
+ "learning_style": extract_field(file_content, ["style", "أسلوب", "تفضيلات"]),
34
+ "goals": extract_field(file_content, ["goals", "أهداف", "هدف"]),
35
+ }
36
+ return profile
37
+
38
+ except Exception as e:
39
+ print(f"خطأ في تحليل الملف: {str(e)}")
40
+
41
+ return None
42
+
43
+
44
+ def extract_field(text: str, keywords: List[str]) -> str:
45
+ """استخراج حقل من نص حر بناءً على كلمات مفتاحية"""
46
+ text_lower = text.lower()
47
+
48
+ for keyword in keywords:
49
+ if keyword.lower() in text_lower:
50
+ # البحث عن السطر الذي يحتوي على الكلمة المفتاحية
51
+ for line in text.split('\n'):
52
+ if keyword.lower() in line.lower():
53
+ # استخراج القيمة بعد النقطتين أو المساواة
54
+ if ':' in line:
55
+ return line.split(':', 1)[1].strip()
56
+ elif '=' in line:
57
+ return line.split('=', 1)[1].strip()
58
+
59
+ return ""
60
+
61
+
62
+ def create_default_profile() -> Dict:
63
+ """إنشاء ملف تعريفي افتراضي"""
64
+ return {
65
+ "name": "مستخدم جديد",
66
+ "age": "",
67
+ "level": "مبتدئ",
68
+ "skills": [],
69
+ "available_hours": 10,
70
+ "learning_style": "مختلط (نظري وعملي)",
71
+ "goals": "تعلم مهارات جديدة",
72
+ "preferences": {
73
+ "language": "ar",
74
+ "notifications": True,
75
+ "theme": "light"
76
+ }
77
+ }
78
+
79
+
80
+ def validate_profile(profile: Dict) -> tuple[bool, List[str]]:
81
+ """
82
+ التحقق من صحة الملف التعريفي
83
+
84
+ Returns:
85
+ (هل صالح, قائمة بالأخطاء)
86
+ """
87
+ errors = []
88
+
89
+ required_fields = ["name", "level", "available_hours"]
90
+ for field in required_fields:
91
+ if field not in profile or not profile[field]:
92
+ errors.append(f"الحقل '{field}' مطلوب")
93
+
94
+ if profile.get("available_hours"):
95
+ try:
96
+ hours = int(profile["available_hours"])
97
+ if hours < 1 or hours > 40:
98
+ errors.append("الوقت المتاح يجب أن يكون بين 1 و 40 ساعة أسبوعياً")
99
+ except:
100
+ errors.append("الوقت المتاح يجب أن يكون رقماً")
101
+
102
+ return len(errors) == 0, errors
103
+
104
+
105
+ def generate_schedule(curriculum: List[Dict], available_hours: int) -> List[Dict]:
106
+ """
107
+ توليد جدول زمني بناءً على المنهج والوقت المتاح
108
+
109
+ Args:
110
+ curriculum: المنهج الدراسي
111
+ available_hours: الساعات المتاحة أسبوعياً
112
+
113
+ Returns:
114
+ جدول زمني مفصل
115
+ """
116
+ schedule = []
117
+ start_date = datetime.now()
118
+
119
+ for week_num, unit in enumerate(curriculum, start=1):
120
+ week_start = start_date + timedelta(weeks=week_num - 1)
121
+
122
+ # توزيع الدروس على أيام الأسبوع
123
+ lessons = unit.get("lessons", [])
124
+ total_mins = sum(lesson.get("duration_mins", 60) for lesson in lessons)
125
+
126
+ # إضافة وقت التجربة
127
+ if unit.get("experiment"):
128
+ total_mins += 120 # افتراض ساعتين للتجربة
129
+
130
+ schedule_entry = {
131
+ "week": week_num,
132
+ "start_date": week_start.strftime("%Y-%m-%d"),
133
+ "unit_title": unit.get("unit_title", f"الوحدة {week_num}"),
134
+ "total_hours": round(total_mins / 60, 1),
135
+ "daily_breakdown": distribute_to_days(lessons, available_hours),
136
+ "experiment": unit.get("experiment"),
137
+ "status": "pending"
138
+ }
139
+
140
+ schedule.append(schedule_entry)
141
+
142
+ return schedule
143
+
144
+
145
+ def distribute_to_days(lessons: List[Dict], hours_per_week: int) -> List[Dict]:
146
+ """توزيع الدروس على أيام الأسبوع"""
147
+ days = ["الأحد", "الاثنين", "الثلاثاء", "الأربعاء", "الخميس"]
148
+ distribution = []
149
+
150
+ mins_per_day = (hours_per_week * 60) // len(days)
151
+
152
+ current_day = 0
153
+ current_day_mins = 0
154
+ current_day_lessons = []
155
+
156
+ for lesson in lessons:
157
+ lesson_mins = lesson.get("duration_mins", 60)
158
+
159
+ if current_day_mins + lesson_mins > mins_per_day and current_day_lessons:
160
+ # حفظ اليوم الحالي والانتقال للتالي
161
+ distribution.append({
162
+ "day": days[current_day % len(days)],
163
+ "lessons": current_day_lessons.copy(),
164
+ "total_mins": current_day_mins
165
+ })
166
+ current_day += 1
167
+ current_day_mins = 0
168
+ current_day_lessons = []
169
+
170
+ current_day_lessons.append(lesson)
171
+ current_day_mins += lesson_mins
172
+
173
+ # إضافة آخر يوم
174
+ if current_day_lessons:
175
+ distribution.append({
176
+ "day": days[current_day % len(days)],
177
+ "lessons": current_day_lessons,
178
+ "total_mins": current_day_mins
179
+ })
180
+
181
+ return distribution
182
+
183
+
184
+ def calculate_progress(completed_units: List[int], total_units: int) -> Dict:
185
+ """
186
+ حساب التقدم الإجمالي
187
+
188
+ Returns:
189
+ معلومات التقدم
190
+ """
191
+ completed = len(completed_units)
192
+ percentage = (completed / total_units * 100) if total_units > 0 else 0
193
+
194
+ return {
195
+ "completed_units": completed,
196
+ "total_units": total_units,
197
+ "percentage": round(percentage, 1),
198
+ "remaining_units": total_units - completed,
199
+ "status": get_progress_status(percentage)
200
+ }
201
+
202
+
203
+ def get_progress_status(percentage: float) -> str:
204
+ """تحديد حالة التقدم"""
205
+ if percentage < 25:
206
+ return "بداية الرحلة 🌱"
207
+ elif percentage < 50:
208
+ return "في الطريق 🚀"
209
+ elif percentage < 75:
210
+ return "تقدم ممتاز 🌟"
211
+ elif percentage < 100:
212
+ return "قرب الإنجاز 🎯"
213
+ else:
214
+ return "مكتمل! 🎉"
215
+
216
+
217
+ def export_profile_to_json(profile: Dict) -> str:
218
+ """تصدير الملف التعريفي إلى JSON"""
219
+ return json.dumps(profile, ensure_ascii=False, indent=2)
220
+
221
+
222
+ def export_progress_to_json(progress_data: Dict) -> str:
223
+ """تصدير بيانات التقدم إلى JSON"""
224
+ return json.dumps(progress_data, ensure_ascii=False, indent=2)
225
+
226
+
227
+ def load_local_data(filename: str) -> Optional[Dict]:
228
+ """تحميل بيانات من ملف JSON محلي"""
229
+ try:
230
+ with open(f"data/{filename}", "r", encoding="utf-8") as f:
231
+ return json.load(f)
232
+ except:
233
+ return None
234
+
235
+
236
+ def save_local_data(filename: str, data: Dict) -> bool:
237
+ """حفظ بيانات في ملف JSON محلي"""
238
+ try:
239
+ with open(f"data/{filename}", "w", encoding="utf-8") as f:
240
+ json.dump(data, f, ensure_ascii=False, indent=2)
241
+ return True
242
+ except:
243
+ return False
244
+
245
+
246
+ def get_completion_stats(completed_lessons: List[str], curriculum: List[Dict]) -> Dict:
247
+ """حساب إحصائيات الإنجاز المفصلة"""
248
+ total_lessons = sum(len(unit.get("lessons", [])) for unit in curriculum)
249
+ completed = len(completed_lessons)
250
+
251
+ total_experiments = len([u for u in curriculum if u.get("experiment")])
252
+ completed_experiments = 0 # يمكن تتبعها بشكل منفصل
253
+
254
+ return {
255
+ "lessons": {
256
+ "completed": completed,
257
+ "total": total_lessons,
258
+ "percentage": round(completed / total_lessons * 100, 1) if total_lessons > 0 else 0
259
+ },
260
+ "experiments": {
261
+ "completed": completed_experiments,
262
+ "total": total_experiments,
263
+ "percentage": round(completed_experiments / total_experiments * 100, 1) if total_experiments > 0 else 0
264
+ },
265
+ "overall_percentage": round((completed + completed_experiments) / (total_lessons + total_experiments) * 100, 1)
266
+ }
utils/ui_utils.py ADDED
@@ -0,0 +1,310 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ وظائف تخصيص واجهة المستخدم (UI utilities)
3
+ """
4
+ import streamlit as st
5
+ from typing import Dict, List, Optional
6
+ import config
7
+
8
+
9
+ def load_custom_css():
10
+ """تحميل CSS مخصص"""
11
+ try:
12
+ with open("assets/css/style.css", "r", encoding="utf-8") as f:
13
+ st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
14
+ except:
15
+ # CSS افتراضي في حالة عدم وجود الملف
16
+ st.markdown("""
17
+ <style>
18
+ @import url('https://fonts.googleapis.com/css2?family=Tajawal:wght@400;500;700&display=swap');
19
+
20
+ * {
21
+ font-family: 'Tajawal', sans-serif !important;
22
+ }
23
+
24
+ .main {
25
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
26
+ background-attachment: fixed;
27
+ }
28
+
29
+ .block-container {
30
+ background: rgba(255, 255, 255, 0.95);
31
+ border-radius: 20px;
32
+ padding: 2rem;
33
+ margin-top: 2rem;
34
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
35
+ }
36
+
37
+ h1, h2, h3 {
38
+ color: #667eea;
39
+ }
40
+
41
+ .stButton > button {
42
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
43
+ color: white;
44
+ border: none;
45
+ border-radius: 10px;
46
+ padding: 0.75rem 2rem;
47
+ font-size: 1.1rem;
48
+ font-weight: 600;
49
+ transition: all 0.3s ease;
50
+ width: 100%;
51
+ }
52
+
53
+ .stButton > button:hover {
54
+ transform: translateY(-2px);
55
+ box-shadow: 0 5px 20px rgba(102, 126, 234, 0.4);
56
+ }
57
+
58
+ .success-box {
59
+ background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
60
+ color: white;
61
+ padding: 1rem;
62
+ border-radius: 10px;
63
+ margin: 1rem 0;
64
+ }
65
+
66
+ .info-box {
67
+ background: linear-gradient(135deg, #4299e1 0%, #3182ce 100%);
68
+ color: white;
69
+ padding: 1rem;
70
+ border-radius: 10px;
71
+ margin: 1rem 0;
72
+ }
73
+
74
+ .warning-box {
75
+ background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%);
76
+ color: white;
77
+ padding: 1rem;
78
+ border-radius: 10px;
79
+ margin: 1rem 0;
80
+ }
81
+
82
+ .card {
83
+ background: white;
84
+ border-radius: 15px;
85
+ padding: 1.5rem;
86
+ margin: 1rem 0;
87
+ box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
88
+ transition: all 0.3s ease;
89
+ }
90
+
91
+ .card:hover {
92
+ transform: translateY(-5px);
93
+ box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
94
+ }
95
+
96
+ .progress-bar {
97
+ background: #e2e8f0;
98
+ border-radius: 10px;
99
+ height: 25px;
100
+ overflow: hidden;
101
+ margin: 1rem 0;
102
+ }
103
+
104
+ .progress-fill {
105
+ background: linear-gradient(90deg, #48bb78 0%, #38a169 100%);
106
+ height: 100%;
107
+ display: flex;
108
+ align-items: center;
109
+ justify-content: center;
110
+ color: white;
111
+ font-weight: 600;
112
+ transition: width 0.5s ease;
113
+ }
114
+
115
+ .badge {
116
+ display: inline-block;
117
+ padding: 0.35rem 0.75rem;
118
+ border-radius: 20px;
119
+ font-size: 0.875rem;
120
+ font-weight: 600;
121
+ margin: 0.25rem;
122
+ }
123
+
124
+ .badge-primary {
125
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
126
+ color: white;
127
+ }
128
+
129
+ .badge-success {
130
+ background: #48bb78;
131
+ color: white;
132
+ }
133
+
134
+ .badge-warning {
135
+ background: #ed8936;
136
+ color: white;
137
+ }
138
+
139
+ .badge-info {
140
+ background: #4299e1;
141
+ color: white;
142
+ }
143
+ </style>
144
+ """, unsafe_allow_html=True)
145
+
146
+
147
+ def show_hero_section(title: str, description: str):
148
+ """عرض قسم Hero جميل"""
149
+ st.markdown(f"""
150
+ <div style='text-align: center; padding: 3rem 1rem;'>
151
+ <h1 style='font-size: 3em; background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
152
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: 0.5rem;'>
153
+ {title}
154
+ </h1>
155
+ <p style='font-size: 1.3rem; color: #4a5568; margin-top: 0;'>
156
+ {description}
157
+ </p>
158
+ </div>
159
+ """, unsafe_allow_html=True)
160
+
161
+
162
+ def show_card(title: str, content: str, icon: str = "📚"):
163
+ """عرض بطاقة جميلة"""
164
+ st.markdown(f"""
165
+ <div class='card'>
166
+ <h3 style='margin-top: 0;'>{icon} {title}</h3>
167
+ <p style='color: #4a5568;'>{content}</p>
168
+ </div>
169
+ """, unsafe_allow_html=True)
170
+
171
+
172
+ def show_feature_cards(features: List[Dict]):
173
+ """عرض بطاقات الميزات في أعمدة"""
174
+ cols = st.columns(len(features))
175
+
176
+ for col, feature in zip(cols, features):
177
+ with col:
178
+ st.markdown(f"""
179
+ <div class='card' style='text-align: center;'>
180
+ <div style='font-size: 3rem; margin-bottom: 0.5rem;'>{feature.get('icon', '✨')}</div>
181
+ <h3 style='margin: 0.5rem 0;'>{feature.get('title', '')}</h3>
182
+ <p style='color: #718096; font-size: 0.95rem;'>{feature.get('description', '')}</p>
183
+ </div>
184
+ """, unsafe_allow_html=True)
185
+
186
+
187
+ def show_progress_bar(percentage: float, label: str = ""):
188
+ """عرض شريط تقدم جميل"""
189
+ st.markdown(f"""
190
+ <div class='progress-bar'>
191
+ <div class='progress-fill' style='width: {percentage}%;'>
192
+ {percentage:.1f}%
193
+ </div>
194
+ </div>
195
+ {f'<p style="text-align: center; color: #4a5568; margin-top: 0.5rem;">{label}</p>' if label else ''}
196
+ """, unsafe_allow_html=True)
197
+
198
+
199
+ def show_badge(text: str, badge_type: str = "primary"):
200
+ """عرض شارة ملونة"""
201
+ return f"<span class='badge badge-{badge_type}'>{text}</span>"
202
+
203
+
204
+ def show_success_message(message: str):
205
+ """رسالة نجاح جميلة"""
206
+ st.markdown(f"""
207
+ <div class='success-box'>
208
+ <strong>✅ {message}</strong>
209
+ </div>
210
+ """, unsafe_allow_html=True)
211
+
212
+
213
+ def show_info_message(message: str):
214
+ """رسالة معلومات جميلة"""
215
+ st.markdown(f"""
216
+ <div class='info-box'>
217
+ <strong>ℹ️ {message}</strong>
218
+ </div>
219
+ """, unsafe_allow_html=True)
220
+
221
+
222
+ def show_warning_message(message: str):
223
+ """رسالة تحذير جميلة"""
224
+ st.markdown(f"""
225
+ <div class='warning-box'>
226
+ <strong>⚠️ {message}</strong>
227
+ </div>
228
+ """, unsafe_allow_html=True)
229
+
230
+
231
+ def create_stat_card(label: str, value: str, icon: str = "📊"):
232
+ """إنشاء بطاقة إحصائية"""
233
+ return f"""
234
+ <div class='card' style='text-align: center;'>
235
+ <div style='font-size: 2.5rem;'>{icon}</div>
236
+ <h2 style='margin: 0.5rem 0; color: #667eea;'>{value}</h2>
237
+ <p style='color: #718096; margin: 0;'>{label}</p>
238
+ </div>
239
+ """
240
+
241
+
242
+ def show_timeline(events: List[Dict]):
243
+ """عرض خط زمني للأحداث"""
244
+ for event in events:
245
+ status_icon = "✅" if event.get("completed") else "⏳"
246
+ st.markdown(f"""
247
+ <div class='card' style='border-right: 4px solid {"#48bb78" if event.get("completed") else "#cbd5e0"};'>
248
+ <div style='display: flex; align-items: center; gap: 1rem;'>
249
+ <div style='font-size: 2rem;'>{status_icon}</div>
250
+ <div style='flex: 1;'>
251
+ <h4 style='margin: 0; color: #2d3748;'>{event.get('title', '')}</h4>
252
+ <p style='margin: 0.25rem 0 0 0; color: #718096; font-size: 0.9rem;'>
253
+ {event.get('date', '')}
254
+ </p>
255
+ </div>
256
+ </div>
257
+ </div>
258
+ """, unsafe_allow_html=True)
259
+
260
+
261
+ def show_hub_selector(hubs: Dict) -> Optional[str]:
262
+ """عرض محدد المحاور التعليمية"""
263
+ st.markdown("### 🎯 اختر محورك التعليمي")
264
+
265
+ selected_hub = None
266
+ cols = st.columns(2)
267
+
268
+ hub_items = list(hubs.items())
269
+ for idx, (hub_id, hub_data) in enumerate(hub_items):
270
+ col = cols[idx % 2]
271
+ with col:
272
+ if st.button(
273
+ f"{hub_data['icon']} {hub_data['name']}\n\n{hub_data['description']}",
274
+ key=f"hub_{hub_id}",
275
+ use_container_width=True
276
+ ):
277
+ selected_hub = hub_id
278
+
279
+ return selected_hub
280
+
281
+
282
+ def init_session_state(defaults: Dict):
283
+ """تهيئة session state بقيم افتراضية"""
284
+ for key, value in defaults.items():
285
+ if key not in st.session_state:
286
+ st.session_state[key] = value
287
+
288
+
289
+ def show_lesson_card(lesson: Dict, lesson_id: str, is_completed: bool = False):
290
+ """عرض بطاقة درس"""
291
+ status_color = "#48bb78" if is_completed else "#cbd5e0"
292
+ status_text = "مكتمل ✅" if is_completed else "قيد الانتظار ⏳"
293
+
294
+ st.markdown(f"""
295
+ <div class='card' style='border-right: 4px solid {status_color};'>
296
+ <div style='display: flex; justify-content: space-between; align-items: start;'>
297
+ <div style='flex: 1;'>
298
+ <h4 style='margin: 0 0 0.5rem 0; color: #2d3748;'>{lesson.get('title', '')}</h4>
299
+ <p style='color: #718096; margin: 0.25rem 0;'>{lesson.get('description', '')}</p>
300
+ <div style='margin-top: 0.75rem;'>
301
+ <span class='badge badge-info'>⏱️ {lesson.get('duration_mins', 0)} دقيقة</span>
302
+ <span class='badge badge-primary'>{lesson.get('type', 'درس')}</span>
303
+ </div>
304
+ </div>
305
+ <div style='text-align: left; min-width: 100px;'>
306
+ <span class='badge badge-{"success" if is_completed else "warning"}'>{status_text}</span>
307
+ </div>
308
+ </div>
309
+ </div>
310
+ """, unsafe_allow_html=True)