Spaces:
Build error
Build error
Upload 5 files
Browse files- app.py +220 -0
- logic.py +368 -0
- requirements.txt +7 -3
- styles.css +51 -0
- translations.py +74 -0
app.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
"""
|
| 3 |
+
====================================================================================================
|
| 4 |
+
📂 FILE: app.py | ملف واجهة المستخدم
|
| 5 |
+
====================================================================================================
|
| 6 |
+
🇬🇧 English Description:
|
| 7 |
+
This is the frontend entry point using Streamlit.
|
| 8 |
+
It handles:
|
| 9 |
+
1. UI Layout & Design (Sidebar, Inputs, Results).
|
| 10 |
+
2. Resource Monitoring (RAM, CPU, GPU Dashboard).
|
| 11 |
+
3. Bilingual Support (Switching between Ar/En).
|
| 12 |
+
4. Calling the backend `logic.py` to process answers.
|
| 13 |
+
|
| 14 |
+
🇪🇬 Arabic Description:
|
| 15 |
+
هذا هو مدخل الواجهة الأمامية باستخدام مكتبة Streamlit.
|
| 16 |
+
يتولى:
|
| 17 |
+
1. تصميم وتخطيط الواجهة (القائمة الجانبية، المدخلات، النتائج).
|
| 18 |
+
2. مراقبة الموارد (لوحة الرامات، المعالج، وكارت الشاشة).
|
| 19 |
+
3. دعم تعدد اللغات (التحويل بين العربية والإنجليزية).
|
| 20 |
+
4. استدعاء الخلفية `logic.py` لمعالجة الإجابات.
|
| 21 |
+
====================================================================================================
|
| 22 |
+
"""
|
| 23 |
+
|
| 24 |
+
import streamlit as st
|
| 25 |
+
import time
|
| 26 |
+
import psutil
|
| 27 |
+
import subprocess
|
| 28 |
+
from logic import load_model, predict, device
|
| 29 |
+
from translations import TRANSLATIONS
|
| 30 |
+
|
| 31 |
+
# ==========================================
|
| 32 |
+
# 📊 RESOURCE MONITOR HELPER | مساعد مراقبة الموارد
|
| 33 |
+
# ==========================================
|
| 34 |
+
def get_system_metrics():
|
| 35 |
+
"""
|
| 36 |
+
🇬🇧 Collects real-time system stats.
|
| 37 |
+
Why? To debug crashes on Colab by monitoring if RAM/VRAM is full.
|
| 38 |
+
|
| 39 |
+
🇪🇬 يجمع إحصائيات النظام لحظياً.
|
| 40 |
+
لماذا؟ لتتبع أسباب الانهيار على Colab عبر مراقبة امتلاء الرامات أو ذاكرة الفيديو.
|
| 41 |
+
"""
|
| 42 |
+
# 🇬🇧 System RAM (Capacity: 12GB on Colab)
|
| 43 |
+
# 🇪🇬 رامات النظام (السعة: 12 جيجا على Colab)
|
| 44 |
+
ram = psutil.virtual_memory()
|
| 45 |
+
ram_used = ram.used / (1024 ** 3) # Convert bytes to GB
|
| 46 |
+
ram_total = ram.total / (1024 ** 3)
|
| 47 |
+
ram_percent = ram.percent
|
| 48 |
+
|
| 49 |
+
# 🇬🇧 CPU Usage (Capacity: 2 Cores on Colab)
|
| 50 |
+
# 🇪🇬 استهلاك المعالج (السعة: نواتين على Colab)
|
| 51 |
+
cpu_percent = psutil.cpu_percent(interval=None)
|
| 52 |
+
|
| 53 |
+
# 🇬🇧 GPU VRAM (Capacity: 16GB on T4) - Via nvidia-smi command
|
| 54 |
+
# 🇪🇬 ذاكرة الفيديو (السعة: 16 جيجا على T4) - عبر أمر nvidia-smi
|
| 55 |
+
gpu_info = "N/A"
|
| 56 |
+
gpu_memory = "N/A"
|
| 57 |
+
try:
|
| 58 |
+
# Run nvidia-smi query to get utilization and memory usage
|
| 59 |
+
result = subprocess.run(
|
| 60 |
+
['nvidia-smi', '--query-gpu=utilization.gpu,memory.used,memory.total', '--format=csv,nounits,noheader'],
|
| 61 |
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
|
| 62 |
+
)
|
| 63 |
+
if result.returncode == 0:
|
| 64 |
+
util, used, total = result.stdout.strip().split(',')
|
| 65 |
+
gpu_info = f"{util.strip()}%"
|
| 66 |
+
gpu_memory = f"{int(used)}MB / {int(total)}MB"
|
| 67 |
+
except:
|
| 68 |
+
pass # Fail silently if no NVIDIA GPU found (e.g. local machine without CUDA)
|
| 69 |
+
|
| 70 |
+
return ram_used, ram_total, ram_percent, cpu_percent, gpu_info, gpu_memory
|
| 71 |
+
|
| 72 |
+
# ==========================================
|
| 73 |
+
# 🎨 PAGE SETUP (Must be first) | إعداد الصفحة (يجب أن يكون أولاً)
|
| 74 |
+
# ==========================================
|
| 75 |
+
st.set_page_config(
|
| 76 |
+
page_title="ASAG Grader AI",
|
| 77 |
+
page_icon="🎓",
|
| 78 |
+
layout="wide",
|
| 79 |
+
initial_sidebar_state="expanded"
|
| 80 |
+
)
|
| 81 |
+
|
| 82 |
+
# 🇬🇧 Load Custom CSS for advanced styling
|
| 83 |
+
# 🇪🇬 تحميل CSS المخصص للتنسيق المتقدم
|
| 84 |
+
with open("styles.css", "r") as f:
|
| 85 |
+
st.markdown(f"<style>{f.read()}</style>", unsafe_allow_html=True)
|
| 86 |
+
|
| 87 |
+
# ==========================================
|
| 88 |
+
# 🌐 LANGUAGE SETUP | إعداد اللغات
|
| 89 |
+
# ==========================================
|
| 90 |
+
# 🇬🇧 Initialize Session State for Language (Default: Arabic)
|
| 91 |
+
# 🇪🇬 تهيئة حالة الجلسة للغة (الافتراضي: العربية)
|
| 92 |
+
if 'lang' not in st.session_state:
|
| 93 |
+
st.session_state.lang = 'ar'
|
| 94 |
+
|
| 95 |
+
def toggle_lang():
|
| 96 |
+
"""Switches between 'ar' and 'en'"""
|
| 97 |
+
if st.session_state.lang == 'ar':
|
| 98 |
+
st.session_state.lang = 'en'
|
| 99 |
+
else:
|
| 100 |
+
st.session_state.lang = 'ar'
|
| 101 |
+
|
| 102 |
+
# 🇬🇧 Sidebar Button to Toggle Language
|
| 103 |
+
# 🇪🇬 زر القائمة الجانبية لتغيير اللغة
|
| 104 |
+
lang_btn_label = "Switch to English 🇺🇸" if st.session_state.lang == 'ar' else "التحويل للعربية 🇪🇬"
|
| 105 |
+
st.sidebar.button(lang_btn_label, on_click=toggle_lang)
|
| 106 |
+
|
| 107 |
+
# 🇬🇧 Select Dictionary based on current language
|
| 108 |
+
# 🇪🇬 اختيار القاموس بناءً على اللغة الحالية
|
| 109 |
+
txt = TRANSLATIONS[st.session_state.lang]
|
| 110 |
+
|
| 111 |
+
# 🇬🇧 Dynamic Directionality (RTL for Arabic, LTR for English)
|
| 112 |
+
# 🇪🇬 اتجاه ديناميكي (يمين لليسار للعربية، يسار لليمين للإنجليزية)
|
| 113 |
+
st.markdown(f"""
|
| 114 |
+
<style>
|
| 115 |
+
.stApp {{ direction: {'rtl' if st.session_state.lang == 'ar' else 'ltr'}; }}
|
| 116 |
+
.stTextInput, .stTextArea {{ direction: {'rtl' if st.session_state.lang == 'ar' else 'ltr'}; }}
|
| 117 |
+
</style>
|
| 118 |
+
""", unsafe_allow_html=True)
|
| 119 |
+
|
| 120 |
+
# ==========================================
|
| 121 |
+
# 📟 SIDEBAR MONITOR | لوحة المراقبة الجانبية
|
| 122 |
+
# ==========================================
|
| 123 |
+
with st.sidebar.expander("📊 System Monitor (Live)", expanded=True):
|
| 124 |
+
r_used, r_total, r_perc, c_perc, g_util, g_mem = get_system_metrics()
|
| 125 |
+
|
| 126 |
+
# RAM Display
|
| 127 |
+
st.caption("💻 **System RAM:**")
|
| 128 |
+
st.progress(r_perc / 100)
|
| 129 |
+
st.write(f"{r_used:.1f}GB / {r_total:.1f}GB ({r_perc}%)")
|
| 130 |
+
|
| 131 |
+
# CPU Display
|
| 132 |
+
st.caption("⚙️ **CPU Usage:**")
|
| 133 |
+
st.progress(c_perc / 100)
|
| 134 |
+
st.write(f"{c_perc}%")
|
| 135 |
+
|
| 136 |
+
# GPU Display
|
| 137 |
+
st.caption("🎮 **GPU VRAM:**")
|
| 138 |
+
st.code(f"Util: {g_util}\nMem: {g_mem}")
|
| 139 |
+
|
| 140 |
+
# Refresh Button
|
| 141 |
+
if st.button("🔄 Refresh Stats"):
|
| 142 |
+
st.rerun()
|
| 143 |
+
|
| 144 |
+
# ==========================================
|
| 145 |
+
# 🧠 MODEL LOADING | تحميل الموديل
|
| 146 |
+
# ==========================================
|
| 147 |
+
with st.spinner(txt["loading_model"]):
|
| 148 |
+
model, tokenizer = load_model(device)
|
| 149 |
+
|
| 150 |
+
if model is None:
|
| 151 |
+
st.error(txt["error_model_not_found"])
|
| 152 |
+
st.stop()
|
| 153 |
+
|
| 154 |
+
# ==========================================
|
| 155 |
+
# 🌌 UI LAYOUT | تخطيط الواجهة
|
| 156 |
+
# ==========================================
|
| 157 |
+
st.sidebar.image("https://cdn-icons-png.flaticon.com/512/4712/4712038.png", width=100)
|
| 158 |
+
st.sidebar.title(txt["sidebar_title"])
|
| 159 |
+
st.sidebar.info(txt["sidebar_info"])
|
| 160 |
+
|
| 161 |
+
st.title(txt["main_title"])
|
| 162 |
+
st.markdown("---")
|
| 163 |
+
|
| 164 |
+
col1, col2 = st.columns(2)
|
| 165 |
+
|
| 166 |
+
with col1:
|
| 167 |
+
question = st.text_area(txt["question_label"], height=100, placeholder=txt["question_placeholder"])
|
| 168 |
+
model_answer = st.text_area(txt["model_answer_label"], height=150, placeholder=txt["model_answer_placeholder"])
|
| 169 |
+
|
| 170 |
+
with col2:
|
| 171 |
+
student_answer = st.text_area(txt["student_answer_label"], height=290, placeholder=txt["student_answer_placeholder"])
|
| 172 |
+
|
| 173 |
+
# 🇬🇧 Analyze Button Logic
|
| 174 |
+
# 🇪🇬 منطق زر التحليل
|
| 175 |
+
if st.button(txt["analyze_btn"]):
|
| 176 |
+
if not (question and model_answer and student_answer):
|
| 177 |
+
st.warning(txt["warning_fill_fields"])
|
| 178 |
+
else:
|
| 179 |
+
# Progress Bar Animation
|
| 180 |
+
my_bar = st.progress(0, text=txt["progress_analyzing"])
|
| 181 |
+
time.sleep(0.5)
|
| 182 |
+
my_bar.progress(50, text=txt["progress_measuring"])
|
| 183 |
+
|
| 184 |
+
# 🇬🇧 Call Backend for Prediction
|
| 185 |
+
# 🇪🇬 استدعاء الخلفية للتوقع
|
| 186 |
+
s1, s2, s3, total, feedback = predict(question, model_answer, student_answer, model, tokenizer, lang=st.session_state.lang)
|
| 187 |
+
|
| 188 |
+
my_bar.progress(100, text=txt["progress_done"])
|
| 189 |
+
time.sleep(0.2)
|
| 190 |
+
my_bar.empty()
|
| 191 |
+
|
| 192 |
+
# ====================
|
| 193 |
+
# 📊 RESULTS DASHBOARD | لوحة النتائج
|
| 194 |
+
# ====================
|
| 195 |
+
st.success(txt["final_score"].format(total=total))
|
| 196 |
+
|
| 197 |
+
# Metrics Row
|
| 198 |
+
m1, m2, m3 = st.columns(3)
|
| 199 |
+
m1.metric(txt["metric_c1"], f"{s1:.2f}/5", delta_color="normal")
|
| 200 |
+
m2.metric(txt["metric_c2"], f"{s2:.2f}/5", delta_color="normal")
|
| 201 |
+
m3.metric(txt["metric_c3"], f"{s3:.2f}/5", delta_color="normal")
|
| 202 |
+
|
| 203 |
+
# Visual Bars
|
| 204 |
+
st.markdown(txt["details_title"])
|
| 205 |
+
st.caption(txt["caption_c1"])
|
| 206 |
+
st.progress(int((s1/5)*100))
|
| 207 |
+
st.caption(txt["caption_c2"])
|
| 208 |
+
st.progress(int((s2/5)*100))
|
| 209 |
+
|
| 210 |
+
# Feedback Box
|
| 211 |
+
st.markdown("---")
|
| 212 |
+
st.markdown(txt["feedback_title"])
|
| 213 |
+
# Prepare feedback HTML (Replace newlines with <br>)
|
| 214 |
+
feedback_html = feedback.replace('\n', '<br>')
|
| 215 |
+
|
| 216 |
+
st.markdown(f"""
|
| 217 |
+
<div class="feedback-box">
|
| 218 |
+
{feedback_html}
|
| 219 |
+
</div>
|
| 220 |
+
""", unsafe_allow_html=True)
|
logic.py
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
"""
|
| 3 |
+
====================================================================================================
|
| 4 |
+
📂 FILE: logic.py | ملف المنطق الأساسي
|
| 5 |
+
====================================================================================================
|
| 6 |
+
🇬🇧 English Description:
|
| 7 |
+
This file handles the backend logic for the ASAG (Automated Short Answer Grading) system.
|
| 8 |
+
It is responsible for:
|
| 9 |
+
1. Loading the fine-tuned DeBERTa model.
|
| 10 |
+
2. Managing system resources (selecting GPU vs CPU).
|
| 11 |
+
3. Preprocessing text (Tokenization).
|
| 12 |
+
4. Running the model to predict scores.
|
| 13 |
+
5. Generating detailed feedback based on the scores.
|
| 14 |
+
|
| 15 |
+
🇪🇬 Arabic Description:
|
| 16 |
+
يتولى هذا الملف المنطق الخلفي لنظام التصحيح الآلي (ASAG).
|
| 17 |
+
وهو مسؤول عن:
|
| 18 |
+
1. تحميل نموذج DeBERTa المدرب مسبقاً.
|
| 19 |
+
2. إدارة موارد النظام (اختيار المعالج الرسومي GPU أو المعالج المركزي CPU).
|
| 20 |
+
3. معالجة النصوص (Tokenization).
|
| 21 |
+
4. تشغيل الموديل لتوقع الدرجات.
|
| 22 |
+
5. توليد تغذية راجعة مفصلة بناءً على الدرجات.
|
| 23 |
+
====================================================================================================
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
import torch
|
| 27 |
+
import torch.nn as nn
|
| 28 |
+
from transformers import AutoTokenizer, AutoModel, AutoConfig
|
| 29 |
+
import gc
|
| 30 |
+
import streamlit as st
|
| 31 |
+
import os
|
| 32 |
+
from datetime import datetime
|
| 33 |
+
|
| 34 |
+
# ==========================================
|
| 35 |
+
# 📝 DIAGNOSTIC LOG | سجل التشخيص
|
| 36 |
+
# ==========================================
|
| 37 |
+
LOG_FILE = os.path.join(os.path.dirname(__file__), "diagnostic_log.txt")
|
| 38 |
+
|
| 39 |
+
def diag_log(msg):
|
| 40 |
+
"""يحفظ رسالة تشخيصية في ملف diagnostic_log.txt مع الوقت."""
|
| 41 |
+
timestamp = datetime.now().strftime("%H:%M:%S")
|
| 42 |
+
line = f"[{timestamp}] {msg}"
|
| 43 |
+
print(line) # طباعة أيضاً في الـ terminal
|
| 44 |
+
with open(LOG_FILE, "a", encoding="utf-8") as f:
|
| 45 |
+
f.write(line + "\n")
|
| 46 |
+
|
| 47 |
+
# ==========================================
|
| 48 |
+
# ⚙️ CONFIGURATION & SELECTION | الإعدادات والاختيارات
|
| 49 |
+
# ==========================================
|
| 50 |
+
|
| 51 |
+
# 🇬🇧 Define the base model name used for training.
|
| 52 |
+
# 🇪🇬 تحديد اسم الموديل الأساسي المستخدم في التدريب.
|
| 53 |
+
MODEL_NAME = "microsoft/deberta-v2-xxlarge"
|
| 54 |
+
|
| 55 |
+
# 🇬🇧 Path to the saved model weights file.
|
| 56 |
+
# 🇪🇬 مسار ملف أوزان الموديل المحفوظ.
|
| 57 |
+
MODEL_PATH = "best_model_xxl.pth"
|
| 58 |
+
|
| 59 |
+
# 🇬🇧 Maximum length for input text tokens.
|
| 60 |
+
# ℹ️ IMPACT: Texts longer than this will be truncated, shorter ones padded.
|
| 61 |
+
# 🇪🇬 الحد الأقصى لطول النص (بالكلمات/الرموز).
|
| 62 |
+
# ℹ️ التأثير: النصوص الأطول من ذلك سيتم قصها، والأقصر سيتم تكميلها بأصفار.
|
| 63 |
+
MAX_LEN = 512
|
| 64 |
+
|
| 65 |
+
# ---------------------------------------------------------
|
| 66 |
+
# 🖥️ DEVICE SELECTION STRATEGY | استراتيجية اختيار الجهاز
|
| 67 |
+
# ---------------------------------------------------------
|
| 68 |
+
# 🇬🇧 We check if a GPU (CUDA) is available. If yes, we use it for speed and memory efficiency.
|
| 69 |
+
# ℹ️ IMPACT: Using GPU allows loading the 1.5B param model into VRAM (16GB on T4),
|
| 70 |
+
# saving System RAM and preventing Colab crashes.
|
| 71 |
+
# 🇪🇬 نتحقق مما إذا كان كارت الشاشة (GPU/CUDA) متاحاً. إذا نعم، نستخدمه للسرعة وكفاءة الذاكرة.
|
| 72 |
+
# ℹ️ التأثير: استخدام GPU يسمح بتحميل الموديل الضخم (1.5 مليار باراميتر) في ذاكرة الفيديو VRAM،
|
| 73 |
+
# مما يوفر رامات النظام ويمنع انهيار الجلسة في Colab.
|
| 74 |
+
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 75 |
+
# device = torch.device('cpu') # ⚠️ UNCOMMENT ONLY FOR LOCAL DEBUGGING ON WINDOWS
|
| 76 |
+
|
| 77 |
+
# ==========================================
|
| 78 |
+
# 🏗️ MODEL ARCHITECTURE CLASS | كلاس هيكل الموديل
|
| 79 |
+
# ==========================================
|
| 80 |
+
class ASAGModelXXL(nn.Module):
|
| 81 |
+
"""
|
| 82 |
+
🇬🇧 Custom PyTorch Module wrapping DeBERTa.
|
| 83 |
+
We add 3 separate Fully Connected (FC) layers (Heads) to predict 3 distinct scores:
|
| 84 |
+
1. Content (C1)
|
| 85 |
+
2. Logic (C2)
|
| 86 |
+
3. Language (C3)
|
| 87 |
+
|
| 88 |
+
🇪🇬 كلاس مخصص يغلف موديل DeBERTa.
|
| 89 |
+
نضيف 3 طبقات كاملة الاتصال (Heads) لتوقع 3 درجات منفصلة:
|
| 90 |
+
1. المحتوى (C1)
|
| 91 |
+
2. المنطق (C2)
|
| 92 |
+
3. اللغة (C3)
|
| 93 |
+
"""
|
| 94 |
+
def __init__(self, model_name, from_config=False):
|
| 95 |
+
super().__init__()
|
| 96 |
+
if from_config:
|
| 97 |
+
# 🇬🇧 Build architecture ONLY (no pretrained weights download = saves ~6GB RAM).
|
| 98 |
+
# 🇪🇬 بناء الهيكل فقط (بدون تحميل أوزان = توفير ~6 جيجا RAM).
|
| 99 |
+
config = AutoConfig.from_pretrained(model_name)
|
| 100 |
+
self.backbone = AutoModel.from_config(config)
|
| 101 |
+
else:
|
| 102 |
+
# 🇬🇧 Load with pretrained weights (only used for training).
|
| 103 |
+
# 🇪🇬 تحميل مع الأوزان (يُستخدم للتدريب فقط).
|
| 104 |
+
self.backbone = AutoModel.from_pretrained(model_name)
|
| 105 |
+
|
| 106 |
+
# 🇬🇧 Define output heads. Each maps the hidden size (1536 for XXL) to 1 score.
|
| 107 |
+
# 🇪🇬 تعريف طبقات الإخراج. كل طبقة تحول البعد الخفي (1536) إلى درجة واحدة.
|
| 108 |
+
self.fc_c1 = nn.Linear(self.backbone.config.hidden_size, 1)
|
| 109 |
+
self.fc_c2 = nn.Linear(self.backbone.config.hidden_size, 1)
|
| 110 |
+
self.fc_c3 = nn.Linear(self.backbone.config.hidden_size, 1)
|
| 111 |
+
|
| 112 |
+
# 🇬🇧 Sigmoid activation to squash outputs between 0 and 1 (for scoring).
|
| 113 |
+
# 🇪🇬 دالة التنشيط Sigmoid لحصر المخرجات بين 0 و 1 (للتقييم).
|
| 114 |
+
self.sigmoid = nn.Sigmoid()
|
| 115 |
+
|
| 116 |
+
# 🇬🇧 Dropout for regularization during training (less relevant in inference).
|
| 117 |
+
# 🇪🇬 Dropout لتقليل الاعتماد الزائد أثناء التدريب.
|
| 118 |
+
self.dropout = nn.Dropout(0.3)
|
| 119 |
+
|
| 120 |
+
def forward(self, input_ids, mask, token_type_ids=None):
|
| 121 |
+
# 🇬🇧 Pass input through DeBERTa.
|
| 122 |
+
# 🇪🇬 تمرير المدخلات عبر DeBERTa.
|
| 123 |
+
out = self.backbone(input_ids=input_ids, attention_mask=mask, token_type_ids=token_type_ids).last_hidden_state[:, 0, :]
|
| 124 |
+
|
| 125 |
+
# 🔍 طباعة تشخيصية: إحصائيات مخرجات الـ backbone
|
| 126 |
+
diag_log(f"🧠 [FWD] dtype بعد backbone: {out.dtype}")
|
| 127 |
+
diag_log(f"🧠 [FWD] الإحصائيات: mean={out.mean().item():.6f}, std={out.std().item():.6f}, min={out.min().item():.6f}, max={out.max().item():.6f}")
|
| 128 |
+
|
| 129 |
+
out = self.dropout(out)
|
| 130 |
+
|
| 131 |
+
# 🇬🇧 Pass the [CLS] embedding to each specific head to get scores.
|
| 132 |
+
# 🇪🇬 تمرير تمثيل الـ [CLS] لكل طبقة متخصصة للحصول على الدرجات.
|
| 133 |
+
return self.sigmoid(self.fc_c1(out)), self.sigmoid(self.fc_c2(out)), self.sigmoid(self.fc_c3(out))
|
| 134 |
+
|
| 135 |
+
# ==========================================
|
| 136 |
+
# 🧠 HYBRID FEEDBACK ENGINE | محرك التغذية الراجعة الهجين
|
| 137 |
+
# ==========================================
|
| 138 |
+
def generate_detailed_feedback(c1, c2, c3, lang="ar"):
|
| 139 |
+
"""
|
| 140 |
+
🇬🇧 Generates text feedback based on numerical scores.
|
| 141 |
+
This logic bridges the gap between "AI numbers" and "Human understanding".
|
| 142 |
+
|
| 143 |
+
🇪🇬 يولد تغذية راجعة نصية بناءً على الدرجات الرقمية.
|
| 144 |
+
هذا المنطق يسد الفجوة بين "أرقام الذكاء الاصطناعي" و"الفهم البشري".
|
| 145 |
+
"""
|
| 146 |
+
feedback = []
|
| 147 |
+
|
| 148 |
+
if lang == "ar":
|
| 149 |
+
# --- C1: Content (50%) | المحتوى العلمي ---
|
| 150 |
+
if c1 >= 4.8:
|
| 151 |
+
feedback.append("✨ **المحتوى العلمي:** ممتاز! إجابة شاملة ودقيقة جداً.")
|
| 152 |
+
elif c1 >= 4.0:
|
| 153 |
+
feedback.append("✅ **المحتوى العلمي:** جيد جداً، لكن يمكن إضافة المزيد من التفاصيل التقنية.")
|
| 154 |
+
elif c1 >= 2.5:
|
| 155 |
+
feedback.append("⚠️ **المحتوى العلمي:** مقبول، لكن ينقصه بعض النقاط الجوهرية. راجع تعريف المفاهيم.")
|
| 156 |
+
else:
|
| 157 |
+
feedback.append("❌ **المحتوى العلمي:** ضعيف. الإجابة لا تغطي المطلوب. يرجى مراجعة الدرس.")
|
| 158 |
+
|
| 159 |
+
# --- C2: Logic (35%) | التسلسل المنطقي ---
|
| 160 |
+
if c2 >= 4.5:
|
| 161 |
+
feedback.append("🧠 **التسلسل المنطقي:** رائع! الأفكار مرتبة بشكل منطقي وسلس.")
|
| 162 |
+
elif c2 < 3.0:
|
| 163 |
+
feedback.append("🔄 **التسلسل المنطقي:** الإجابة تحتاج لترتيب أفضل. حاول ربط الأسباب بالنتائج.")
|
| 164 |
+
|
| 165 |
+
# --- C3: Language (15%) | اللغة والأسلوب ---
|
| 166 |
+
if c3 < 3.5:
|
| 167 |
+
feedback.append("📝 **اللغة والأسلوب:** انتبه للأخطاء الإملائية والنحوية. الصياغة تحتاج لتحسين.")
|
| 168 |
+
else:
|
| 169 |
+
feedback.append("✍️ **اللغة والأسلوب:** صياغة سليمة وواضحة.")
|
| 170 |
+
|
| 171 |
+
else: # English Feedback 🇬🇧
|
| 172 |
+
# --- C1: Content (50%) ---
|
| 173 |
+
if c1 >= 4.8:
|
| 174 |
+
feedback.append("✨ **Content Accuracy:** Excellent! Comprehensive and very accurate answer.")
|
| 175 |
+
elif c1 >= 4.0:
|
| 176 |
+
feedback.append("✅ **Content Accuracy:** Very good, but could add more technical details.")
|
| 177 |
+
elif c1 >= 2.5:
|
| 178 |
+
feedback.append("⚠️ **Content Accuracy:** Acceptable, but misses some key points. Review the concepts.")
|
| 179 |
+
else:
|
| 180 |
+
feedback.append("❌ **Content Accuracy:** Weak. The answer does not cover the requirements. Please review the lesson.")
|
| 181 |
+
|
| 182 |
+
# --- C2: Logic (35%) ---
|
| 183 |
+
if c2 >= 4.5:
|
| 184 |
+
feedback.append("🧠 **Logical Flow:** Great! Ideas are organized logically and smoothly.")
|
| 185 |
+
elif c2 < 3.0:
|
| 186 |
+
feedback.append("🔄 **Logical Flow:** The answer needs better organization. Try to link causes to effects.")
|
| 187 |
+
|
| 188 |
+
# --- C3: Language (15%) ---
|
| 189 |
+
if c3 < 3.5:
|
| 190 |
+
feedback.append("📝 **Language & Style:** Watch out for spelling and grammar mistakes. Phrasing needs improvement.")
|
| 191 |
+
else:
|
| 192 |
+
feedback.append("✍️ **Language & Style:** Clear and correct phrasing.")
|
| 193 |
+
|
| 194 |
+
return "\n\n".join(feedback)
|
| 195 |
+
|
| 196 |
+
# ==========================================
|
| 197 |
+
# 🚀 LOAD & PREDICT | التحميل والتوقع
|
| 198 |
+
# ==========================================
|
| 199 |
+
@st.cache_resource
|
| 200 |
+
def load_model(device_obj):
|
| 201 |
+
"""
|
| 202 |
+
🇬🇧 Loads the model into memory. Uses st.cache_resource to prevent reloading on every interaction.
|
| 203 |
+
ℹ️ IMPACT: Huge performance boost. Without caching, every click would take 2+ minutes to reload 5GB model.
|
| 204 |
+
|
| 205 |
+
🇪🇬 يحمل الموديل في الذاكرة. يستخدم st.cache_resource لمنع إعادة التحميل مع كل تفاعل.
|
| 206 |
+
ℹ️ التأثير: تحسين هائل في الأداء. بدون التخزين المؤقت، كل ضغطة ستستغرق دقيقتين لإعادة تحميل 5 جيجا.
|
| 207 |
+
"""
|
| 208 |
+
# 🇬🇧 Check for model file recursively (current dir or parent dir).
|
| 209 |
+
# 🇪🇬 البحث عن ملف الموديل بشكل تكراري (المجلد الحالي أو المجلد الأب).
|
| 210 |
+
path_to_check = os.path.join(os.path.dirname(__file__), "..", MODEL_PATH)
|
| 211 |
+
|
| 212 |
+
if not os.path.exists(path_to_check):
|
| 213 |
+
path_to_check = os.path.join(os.path.dirname(__file__), MODEL_PATH)
|
| 214 |
+
|
| 215 |
+
if not os.path.exists(path_to_check):
|
| 216 |
+
return None, None
|
| 217 |
+
|
| 218 |
+
# 🇬🇧 Load Tokenizer
|
| 219 |
+
# 🇪🇬 تحميل الـ Tokenizer
|
| 220 |
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
|
| 221 |
+
|
| 222 |
+
# ===== مسح الـ log القديم =====
|
| 223 |
+
with open(LOG_FILE, "w", encoding="utf-8") as f:
|
| 224 |
+
f.write(f"=== بدء التشخيص {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ===\n")
|
| 225 |
+
|
| 226 |
+
# =====================================================================
|
| 227 |
+
# ⚡ طريقة التحميل المحسّنة (Memory-Mapped):
|
| 228 |
+
# 1. بناء النموذج من Config (3.6GB أوزان عشوائية على CPU)
|
| 229 |
+
# 2. تحميل checkpoint بـ mmap=True (يقرأ من الملف مباشرة، بدون تحميل كامل في RAM)
|
| 230 |
+
# 3. load_state_dict ينسخ الأوزان واحدة واحدة (peak RAM ≈ 3.6GB + بضع MB)
|
| 231 |
+
# 4. الأوزان تبقى FP32 بالكامل (بدون فقدان دقة)
|
| 232 |
+
# =====================================================================
|
| 233 |
+
|
| 234 |
+
# ===== الخطوة 1: بناء هيكل النموذج =====
|
| 235 |
+
diag_log("🔍 [LOAD] الخطوة 1: بناء هيكل النموذج (from_config)...")
|
| 236 |
+
model = ASAGModelXXL(MODEL_NAME, from_config=True)
|
| 237 |
+
gc.collect()
|
| 238 |
+
diag_log("✅ [LOAD] الهيكل جاهز")
|
| 239 |
+
|
| 240 |
+
# ===== الخطوة 2: تحميل الأوزان بـ mmap (بدون استهلاك RAM إضافي) =====
|
| 241 |
+
diag_log("🔍 [LOAD] الخطوة 2: تحميل الأوزان (mmap - قراءة من الملف مباشرة)...")
|
| 242 |
+
try:
|
| 243 |
+
checkpoint = torch.load(path_to_check, map_location='cpu', mmap=True)
|
| 244 |
+
except Exception as e:
|
| 245 |
+
diag_log(f"❌ [LOAD] فشل تحميل الأوزان: {e}")
|
| 246 |
+
return None, None
|
| 247 |
+
|
| 248 |
+
sample_key = list(checkpoint.keys())[0]
|
| 249 |
+
diag_log(f"✅ [LOAD] تم تحميل {len(checkpoint)} مفتاح | نوع: {checkpoint[sample_key].dtype}")
|
| 250 |
+
|
| 251 |
+
# ===== الخطوة 3: تحميل الأوزان في النموذج =====
|
| 252 |
+
diag_log("🔍 [LOAD] الخطوة 3: تحميل الأوزان في النموذج...")
|
| 253 |
+
result = model.load_state_dict(checkpoint, strict=False)
|
| 254 |
+
if result.missing_keys:
|
| 255 |
+
diag_log(f"⚠️ [LOAD] مفاتيح ناقصة ({len(result.missing_keys)}): {result.missing_keys[:5]}")
|
| 256 |
+
if result.unexpected_keys:
|
| 257 |
+
diag_log(f"⚠️ [LOAD] مفاتيح زائدة ({len(result.unexpected_keys)}): {result.unexpected_keys[:5]}")
|
| 258 |
+
if not result.missing_keys and not result.unexpected_keys:
|
| 259 |
+
diag_log("✅ [LOAD] جميع المفاتيح متطابقة")
|
| 260 |
+
|
| 261 |
+
diag_log(f"🔍 [LOAD] نوع الأوزان: {next(model.backbone.parameters()).dtype}")
|
| 262 |
+
|
| 263 |
+
# ===== تحرير ذاكرة الـ checkpoint =====
|
| 264 |
+
del checkpoint
|
| 265 |
+
gc.collect()
|
| 266 |
+
diag_log("🗑️ [LOAD] تم تحرير الـ checkpoint")
|
| 267 |
+
|
| 268 |
+
# ===== الخطوة 4: نقل للـ GPU =====
|
| 269 |
+
diag_log("🔍 [LOAD] الخطوة 4: نقل النموذج للـ GPU...")
|
| 270 |
+
model.to(device_obj)
|
| 271 |
+
diag_log(f"✅ [LOAD] الجهاز: {device_obj} | dtype: {next(model.backbone.parameters()).dtype}")
|
| 272 |
+
|
| 273 |
+
if device_obj.type == 'cuda':
|
| 274 |
+
gpu_mem = torch.cuda.memory_allocated() / (1024**3)
|
| 275 |
+
diag_log(f"💾 [LOAD] ذاكرة GPU المستخدمة: {gpu_mem:.2f} GB")
|
| 276 |
+
|
| 277 |
+
diag_log("🎉 [LOAD] تم تحميل النموذج بنجاح!")
|
| 278 |
+
|
| 279 |
+
model.eval()
|
| 280 |
+
return model, tokenizer
|
| 281 |
+
|
| 282 |
+
def predict(question, model_answer, student_answer, model, tokenizer, lang="ar"):
|
| 283 |
+
"""
|
| 284 |
+
🇬🇧 The Core Inference Function.
|
| 285 |
+
1. Prepares input string (Question + Model Answer [SEP] Student Answer).
|
| 286 |
+
2. Tokenizes.
|
| 287 |
+
3. Runs Model.
|
| 288 |
+
4. Calculates Weighted Score.
|
| 289 |
+
|
| 290 |
+
🇪🇬 دالة الاستنتاج الأساسية.
|
| 291 |
+
1. تجهيز النص المدخل (السؤال + الإجابة النموذجية [SEP] إجابة الطالب).
|
| 292 |
+
2. الترميز (Tokenization).
|
| 293 |
+
3. تشغيل الموديل.
|
| 294 |
+
4. حساب الدرجة الموزونة.
|
| 295 |
+
"""
|
| 296 |
+
# 🇬🇧 Manual tokenization to guarantee [CLS] + [SEP] + correct token_type_ids
|
| 297 |
+
# 🇪🇬 ترميز يدوي لضمان وجود [CLS] و [SEP] و token_type_ids صحيحة
|
| 298 |
+
# بعض نسخ transformers لا تضيف special tokens تلقائياً لـ DeBERTa
|
| 299 |
+
|
| 300 |
+
cls_id = tokenizer.cls_token_id # عادة 1
|
| 301 |
+
sep_id = tokenizer.sep_token_id # عادة 2
|
| 302 |
+
pad_id = tokenizer.pad_token_id # عادة 0
|
| 303 |
+
|
| 304 |
+
# ترميز كل جزء بدون special tokens
|
| 305 |
+
tokens_a = tokenizer.encode(question + " " + model_answer, add_special_tokens=False)
|
| 306 |
+
tokens_b = tokenizer.encode(student_answer, add_special_tokens=False)
|
| 307 |
+
|
| 308 |
+
# بناء: [CLS] tokens_a [SEP] tokens_b [SEP]
|
| 309 |
+
input_ids = [cls_id] + tokens_a + [sep_id] + tokens_b + [sep_id]
|
| 310 |
+
# token_type_ids: 0 للسؤال+الإجابة النموذجية، 1 لإجابة الطالب
|
| 311 |
+
token_type_ids = [0] * (1 + len(tokens_a) + 1) + [1] * (len(tokens_b) + 1)
|
| 312 |
+
|
| 313 |
+
# قص إذا أطول من MAX_LEN
|
| 314 |
+
if len(input_ids) > MAX_LEN:
|
| 315 |
+
input_ids = input_ids[:MAX_LEN]
|
| 316 |
+
token_type_ids = token_type_ids[:MAX_LEN]
|
| 317 |
+
|
| 318 |
+
# Padding
|
| 319 |
+
actual_len = len(input_ids)
|
| 320 |
+
attention_mask = [1] * actual_len + [0] * (MAX_LEN - actual_len)
|
| 321 |
+
token_type_ids = token_type_ids + [0] * (MAX_LEN - actual_len)
|
| 322 |
+
input_ids = input_ids + [pad_id] * (MAX_LEN - actual_len)
|
| 323 |
+
|
| 324 |
+
inputs = {
|
| 325 |
+
'input_ids': torch.tensor([input_ids]),
|
| 326 |
+
'attention_mask': torch.tensor([attention_mask]),
|
| 327 |
+
'token_type_ids': torch.tensor([token_type_ids])
|
| 328 |
+
}
|
| 329 |
+
|
| 330 |
+
# 🔍 تشخيص
|
| 331 |
+
diag_log(f"📝 [INPUT] tokens: {actual_len}/{MAX_LEN} | CLS={cls_id} SEP={sep_id}")
|
| 332 |
+
diag_log(f"📝 [INPUT] أول 15 token: {inputs['input_ids'][0][:15].tolist()}")
|
| 333 |
+
diag_log(f"📝 [INPUT] token_type_ids (أول 15): {inputs['token_type_ids'][0][:15].tolist()}")
|
| 334 |
+
# موقع SEP (بداية إجابة الطالب)
|
| 335 |
+
sep_pos = 1 + len(tokens_a)
|
| 336 |
+
diag_log(f"📝 [INPUT] SEP في الموقع {sep_pos} | tokens_a={len(tokens_a)} tokens_b={len(tokens_b)}")
|
| 337 |
+
|
| 338 |
+
# 🇬🇧 Disable Gradient Calculation (Save Memory & Speed Up Inference)
|
| 339 |
+
# 🇪🇬 تعطيل حساب التفاضل (توفير الذاكرة وتسريع الاستنتاج)
|
| 340 |
+
with torch.no_grad():
|
| 341 |
+
# 🇬🇧 Disable autocast to prevent automatic FP16 conversion during inference.
|
| 342 |
+
# 🇪🇬 تعطيل الـ autocast لمنع التحويل التلقائي لـ FP16 أثناء الاستنتاج.
|
| 343 |
+
with torch.cuda.amp.autocast(enabled=False):
|
| 344 |
+
c1, c2, c3 = model(
|
| 345 |
+
inputs['input_ids'].to(device),
|
| 346 |
+
inputs['attention_mask'].to(device),
|
| 347 |
+
inputs['token_type_ids'].to(device)
|
| 348 |
+
)
|
| 349 |
+
|
| 350 |
+
# 🔍 طباعة تشخيصية للمخرجات الخام (قبل الضرب في 5)
|
| 351 |
+
diag_log(f"📊 [RAW] c1={c1.item():.6f}, c2={c2.item():.6f}, c3={c3.item():.6f}")
|
| 352 |
+
|
| 353 |
+
# 🇬🇧 Convert 0-1 sigmoid output to 0-5 scale
|
| 354 |
+
# 🇪🇬 تحويل مخرجات Sigmoid (0-1) إلى مقياس (0-5)
|
| 355 |
+
s1 = c1.item() * 5.0
|
| 356 |
+
s2 = c2.item() * 5.0
|
| 357 |
+
s3 = c3.item() * 5.0
|
| 358 |
+
|
| 359 |
+
# 🇬🇧 Calculate Final Weighted Score
|
| 360 |
+
# ℹ️ FORMULA: 50% Content + 35% Logic + 15% Language
|
| 361 |
+
# 🇪🇬 حساب الدرجة النهائية الموزونة
|
| 362 |
+
# ℹ️ المعادلة: 50% محتوى + 35% منطق + 15% لغة
|
| 363 |
+
total = (s1 * 0.50) + (s2 * 0.35) + (s3 * 0.15)
|
| 364 |
+
|
| 365 |
+
# 🔍 طباعة الدرجات النهائية
|
| 366 |
+
diag_log(f"📊 [SCORE] s1={s1:.4f}, s2={s2:.4f}, s3={s3:.4f}, total={total:.4f}")
|
| 367 |
+
|
| 368 |
+
return s1, s2, s3, total, generate_detailed_feedback(s1, s2, s3, lang)
|
requirements.txt
CHANGED
|
@@ -1,3 +1,7 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
torchvision
|
| 3 |
+
torchaudio
|
| 4 |
+
transformers
|
| 5 |
+
sentencepiece
|
| 6 |
+
protobuf
|
| 7 |
+
psutil
|
styles.css
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Google Fonts */
|
| 2 |
+
@import url('https://fonts.googleapis.com/css2?family=Cairo:wght@400;700&display=swap');
|
| 3 |
+
|
| 4 |
+
html, body, [class*="css"] {
|
| 5 |
+
font-family: 'Cairo', sans-serif;
|
| 6 |
+
direction: rtl; /* Right To Left Support */
|
| 7 |
+
}
|
| 8 |
+
|
| 9 |
+
/* Titles */
|
| 10 |
+
h1, h2, h3 {
|
| 11 |
+
color: #4CAF50; /* Green Accent */
|
| 12 |
+
text-align: right;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
/* Text Areas */
|
| 16 |
+
.stTextArea textarea {
|
| 17 |
+
background-color: #1E1E1E;
|
| 18 |
+
color: #ffffff;
|
| 19 |
+
border-radius: 10px;
|
| 20 |
+
border: 1px solid #333;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
/* Buttons */
|
| 24 |
+
.stButton button {
|
| 25 |
+
background-color: #4CAF50;
|
| 26 |
+
color: white;
|
| 27 |
+
width: 100%;
|
| 28 |
+
border-radius: 12px;
|
| 29 |
+
font-weight: bold;
|
| 30 |
+
font-size: 18px;
|
| 31 |
+
transition: 0.3s;
|
| 32 |
+
}
|
| 33 |
+
.stButton button:hover {
|
| 34 |
+
background-color: #45a049;
|
| 35 |
+
scale: 1.02;
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
/* Metric Cards */
|
| 39 |
+
div[data-testid="stMetricValue"] {
|
| 40 |
+
font-size: 2rem;
|
| 41 |
+
color: #FFD700; /* Gold */
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/* Feedback Box */
|
| 45 |
+
.feedback-box {
|
| 46 |
+
background-color: #262730;
|
| 47 |
+
padding: 20px;
|
| 48 |
+
border-radius: 10px;
|
| 49 |
+
border-right: 5px solid #4CAF50;
|
| 50 |
+
margin-top: 20px;
|
| 51 |
+
}
|
translations.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Multi-language Dictionary
|
| 2 |
+
|
| 3 |
+
TRANSLATIONS = {
|
| 4 |
+
"ar": {
|
| 5 |
+
"page_title": "نظام تصحيح الإجابات الذكي",
|
| 6 |
+
"sidebar_title": "🎓 نظام التصحيح الآلي",
|
| 7 |
+
"sidebar_info": """
|
| 8 |
+
**النظام الآلي لتصحيح الإجابات المقالية**
|
| 9 |
+
|
| 10 |
+
مدعوم بواسطة:
|
| 11 |
+
- **DeBERTa-v3-Large** (Fine-tuned).
|
| 12 |
+
- **محرك تغذية راجعة هجين**.
|
| 13 |
+
|
| 14 |
+
تطوير: **فريق المشروع**
|
| 15 |
+
""",
|
| 16 |
+
"main_title": "🧙♂️ المصحح الذكي (AI Grader)",
|
| 17 |
+
"question_label": "✍️ نص السؤال:",
|
| 18 |
+
"question_placeholder": "اكتب السؤال هنا...",
|
| 19 |
+
"model_answer_label": "🔑 الإجابة النموذجية:",
|
| 20 |
+
"model_answer_placeholder": "الإجابة الصحيحة...",
|
| 21 |
+
"student_answer_label": "👨🎓 إجابة الطالب:",
|
| 22 |
+
"student_answer_placeholder": "الإجابة المراد تصحيحها...",
|
| 23 |
+
"analyze_btn": "🚀 تحليل وتصحيح الإجابة",
|
| 24 |
+
"warning_fill_fields": "⚠️ يرجى ملء جميع الحقول أولاً!",
|
| 25 |
+
"progress_analyzing": "جاري تحليل المعنى...",
|
| 26 |
+
"progress_measuring": "قياس التشابه الدلالي...",
|
| 27 |
+
"progress_done": "تم الانتهاء!",
|
| 28 |
+
"final_score": "🏆 الدرجة النهائية: **{total:.2f} / 5.0**",
|
| 29 |
+
"metric_c1": "دقة المحتوى (C1)",
|
| 30 |
+
"metric_c2": "التسلسل المنطقي (C2)",
|
| 31 |
+
"metric_c3": "سلامة اللغة (C3)",
|
| 32 |
+
"details_title": "### 📊 تفاصيل التقييم:",
|
| 33 |
+
"caption_c1": "دقة المحتوى (علمياً)",
|
| 34 |
+
"caption_c2": "المنطق وتسلسل الأفكار",
|
| 35 |
+
"feedback_title": "### 💬 التغذية الراجعة (AI Feedback):",
|
| 36 |
+
"error_model_not_found": "🚨 خطأ: ملف الموديل `best_model_xxl.pth` غير موجود! يرجى تحميله أولاً.",
|
| 37 |
+
"loading_model": "⏳ جارٍ تحميل نموذج الذكاء الاصطناعي (XXL)... يرجى الانتظار..."
|
| 38 |
+
},
|
| 39 |
+
"en": {
|
| 40 |
+
"page_title": "AI ASAG System",
|
| 41 |
+
"sidebar_title": "🎓 ASAG System",
|
| 42 |
+
"sidebar_info": """
|
| 43 |
+
**Automated Short Answer Grading System**
|
| 44 |
+
|
| 45 |
+
Powered by:
|
| 46 |
+
- **DeBERTa-v3-Large** (Fine-tuned).
|
| 47 |
+
- **Hybrid Feedback Engine**.
|
| 48 |
+
|
| 49 |
+
Developed by: **Project Team**
|
| 50 |
+
""",
|
| 51 |
+
"main_title": "🧙♂️ AI Grader",
|
| 52 |
+
"question_label": "✍️ Question Text:",
|
| 53 |
+
"question_placeholder": "Enter the question here...",
|
| 54 |
+
"model_answer_label": "🔑 Model Answer:",
|
| 55 |
+
"model_answer_placeholder": "The correct answer...",
|
| 56 |
+
"student_answer_label": "👨🎓 Student Answer:",
|
| 57 |
+
"student_answer_placeholder": "Answer to be graded...",
|
| 58 |
+
"analyze_btn": "🚀 Analyze & Grade",
|
| 59 |
+
"warning_fill_fields": "⚠️ Please fill in all fields first!",
|
| 60 |
+
"progress_analyzing": "Analyzing semantics...",
|
| 61 |
+
"progress_measuring": "Measuring similarity...",
|
| 62 |
+
"progress_done": "Done!",
|
| 63 |
+
"final_score": "🏆 Final Score: **{total:.2f} / 5.0**",
|
| 64 |
+
"metric_c1": "Content Accuracy (C1)",
|
| 65 |
+
"metric_c2": "Logical Flow (C2)",
|
| 66 |
+
"metric_c3": "Language Quality (C3)",
|
| 67 |
+
"details_title": "### 📊 Grading Details:",
|
| 68 |
+
"caption_c1": "Content Accuracy (Scientific)",
|
| 69 |
+
"caption_c2": "Logic & Flow",
|
| 70 |
+
"feedback_title": "### 💬 AI Feedback:",
|
| 71 |
+
"error_model_not_found": "🚨 Error: Model file `best_model_xxl.pth` not found! Please download it first.",
|
| 72 |
+
"loading_model": "⏳ Loading AI Model (XXL)... Please wait..."
|
| 73 |
+
}
|
| 74 |
+
}
|