Spaces:
Paused
Paused
Update app.py
Browse files
app.py
CHANGED
|
@@ -5,6 +5,7 @@ import os
|
|
| 5 |
import re
|
| 6 |
import shutil
|
| 7 |
import json
|
|
|
|
| 8 |
from datetime import datetime, timedelta
|
| 9 |
from urllib.parse import unquote
|
| 10 |
|
|
@@ -25,12 +26,14 @@ os.makedirs("system", exist_ok=True)
|
|
| 25 |
|
| 26 |
REPO_ID = "Asem75/aiocr_asistant"
|
| 27 |
|
| 28 |
-
RAW_DIR = "RAW_FILES"
|
| 29 |
WATCH_DIR_1 = "process"
|
| 30 |
WATCH_DIR_2 = "process_files"
|
| 31 |
|
| 32 |
FINAL_DIR = "Final_Arabic_Files"
|
| 33 |
SYSTEM_STATUS_PATH = "system/status.json"
|
|
|
|
|
|
|
| 34 |
|
| 35 |
TOKEN = os.getenv("AIOCR_KEY")
|
| 36 |
if not TOKEN:
|
|
@@ -56,6 +59,115 @@ def sanitize_filename(name: str) -> str:
|
|
| 56 |
name = name.replace(" ", "_")
|
| 57 |
return re.sub(r"[^\w\.\-]", "", name)
|
| 58 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 59 |
# ============================================================
|
| 60 |
# 2) رادار فحص القيود للمشترك المجاني (FREE)
|
| 61 |
# ============================================================
|
|
@@ -122,35 +234,45 @@ def read_status_json():
|
|
| 122 |
with open("./system/status.json", "r", encoding="utf-8") as f:
|
| 123 |
data = json.load(f)
|
| 124 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
html = f"""
|
| 126 |
-
<div
|
| 127 |
-
|
| 128 |
-
<b>
|
|
|
|
| 129 |
</div>
|
| 130 |
"""
|
| 131 |
return html
|
| 132 |
except Exception as e:
|
| 133 |
log_error(f"STATUS READ ERROR: {e}")
|
| 134 |
-
return f"<div style='color:orange;'>⚠️ لا يمكن قراءة ملف status.json حالياً — {e}</div>"
|
| 135 |
|
| 136 |
# ============================================================
|
| 137 |
# 4) معالجة رفع الملفات مع دعم جميع البادئات
|
| 138 |
# ============================================================
|
| 139 |
|
| 140 |
-
def handle_upload(files,
|
| 141 |
-
enable_table, enable_handwriting,
|
| 142 |
-
enable_heavy_prep, enable_high_dpi,
|
| 143 |
enable_fast, enable_tashkeel):
|
| 144 |
-
if not files
|
| 145 |
-
return "<div>❌ يرجى اختيار ملفات
|
| 146 |
-
|
| 147 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
is_vip = (rank == "vip")
|
| 149 |
-
|
| 150 |
if not is_vip:
|
| 151 |
allowed, message = check_free_user_limits(username)
|
| 152 |
if not allowed:
|
| 153 |
-
return f"<div style='color:red; font-weight:bold;'>{message}</div>"
|
| 154 |
|
| 155 |
success = 0
|
| 156 |
prefix = f"VIP_{username}_" if is_vip else f"{username}_"
|
|
@@ -161,7 +283,7 @@ def handle_upload(files, username, rank,
|
|
| 161 |
file_size = os.path.getsize(file.name)
|
| 162 |
|
| 163 |
if not is_vip and file_size > 3 * 1024 * 1024:
|
| 164 |
-
return "<div style='color:red;'>❌ خطأ: أحد الملفات يتجاوز حجمه 3 ميجابايت!</div>"
|
| 165 |
|
| 166 |
# بناء بادئة الخيارات
|
| 167 |
option_prefix = ""
|
|
@@ -200,7 +322,16 @@ def handle_upload(files, username, rank,
|
|
| 200 |
except Exception as e:
|
| 201 |
log_error(f"UPLOAD ERROR: {file.name} — {e}")
|
| 202 |
|
| 203 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
|
| 205 |
# ============================================================
|
| 206 |
# 5) إدارة وقراءة ملفات Final والتحويل (باستخدام hf_hub_download)
|
|
@@ -259,6 +390,31 @@ def do_zip(text, base_name):
|
|
| 259 |
except Exception as e:
|
| 260 |
return "⚠️ فشل إنشاء ZIP", None
|
| 261 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
# ============================================================
|
| 263 |
# 6) واجهات Gradio مع جميع الخيارات
|
| 264 |
# ============================================================
|
|
@@ -266,8 +422,18 @@ def do_zip(text, base_name):
|
|
| 266 |
with gr.Blocks(title="📤 بوابة الرفع") as upload_page:
|
| 267 |
gr.Markdown("<h1 style='text-align:center;'>📤 بوابة الرفع ودورة حياة الملفات</h1>")
|
| 268 |
|
| 269 |
-
|
| 270 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 271 |
|
| 272 |
uploader = gr.File(label="اختر ملفاتك", file_count="multiple")
|
| 273 |
|
|
@@ -300,22 +466,32 @@ with gr.Blocks(title="📤 بوابة الرفع") as upload_page:
|
|
| 300 |
status_box = gr.HTML("<div>بانتظار رفع الملفات...</div>")
|
| 301 |
|
| 302 |
gr.Markdown("### 📜 حالة التجميع والمعالجة الفورية")
|
| 303 |
-
system_status = gr.HTML("<div>بانتظار تحديث الحالة من system/status.json...</div>")
|
|
|
|
|
|
|
|
|
|
| 304 |
|
| 305 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
upload_btn.click(
|
| 307 |
fn=handle_upload,
|
| 308 |
-
inputs=[uploader,
|
| 309 |
enable_table, enable_handwriting,
|
| 310 |
enable_heavy_prep, enable_high_dpi,
|
| 311 |
enable_fast, enable_tashkeel],
|
| 312 |
outputs=status_box
|
| 313 |
-
)
|
| 314 |
|
| 315 |
status_timer = gr.Timer(3.0)
|
| 316 |
status_timer.tick(read_status_json, None, system_status)
|
| 317 |
|
| 318 |
-
# ------------------- قسم ملفاتي المُصح
|
| 319 |
with gr.Blocks(title="📁 ملفاتي") as files_page:
|
| 320 |
gr.Markdown("<h1 style='text-align:center;'>📁 الملفات الجاهزة للتحويل</h1>")
|
| 321 |
files_list = gr.Dropdown(label="اختر ملفاً من Final_Arabic_Files", choices=[], interactive=True)
|
|
@@ -359,4 +535,4 @@ with gr.Blocks(title="📁 ملفاتي") as files_page:
|
|
| 359 |
app = gr.TabbedInterface([upload_page, files_page], ["📤 الرفع المباشر", "📁 ملفاتي"])
|
| 360 |
|
| 361 |
if __name__ == "__main__":
|
| 362 |
-
app.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
| 5 |
import re
|
| 6 |
import shutil
|
| 7 |
import json
|
| 8 |
+
import secrets
|
| 9 |
from datetime import datetime, timedelta
|
| 10 |
from urllib.parse import unquote
|
| 11 |
|
|
|
|
| 26 |
|
| 27 |
REPO_ID = "Asem75/aiocr_asistant"
|
| 28 |
|
| 29 |
+
RAW_DIR = "RAW_FILES"
|
| 30 |
WATCH_DIR_1 = "process"
|
| 31 |
WATCH_DIR_2 = "process_files"
|
| 32 |
|
| 33 |
FINAL_DIR = "Final_Arabic_Files"
|
| 34 |
SYSTEM_STATUS_PATH = "system/status.json"
|
| 35 |
+
ABOUT_APP_PATH = "system/about_app.json" # ✅ جديد: تفاصيل الاشتراك / محتوى يكتبه المدير
|
| 36 |
+
MESSAGES_PATH = "system/messages.json" # ✅ جديد: قناة تقارير الواجهة لمكتب المدير
|
| 37 |
|
| 38 |
TOKEN = os.getenv("AIOCR_KEY")
|
| 39 |
if not TOKEN:
|
|
|
|
| 59 |
name = name.replace(" ", "_")
|
| 60 |
return re.sub(r"[^\w\.\-]", "", name)
|
| 61 |
|
| 62 |
+
def generate_guest_name() -> str:
|
| 63 |
+
"""
|
| 64 |
+
🧩 توليد معرّف مؤقت لكل جلسة بانتظار دمج "كود التعرف" مستقبلاً
|
| 65 |
+
(الاسم + البريد الإلكتروني + بصمة عتاد الجهاز + الرتبة).
|
| 66 |
+
عند إضافة ذلك الكود، يكفي تمرير بياناته الحقيقية إلى user_session
|
| 67 |
+
بدلاً من هذا المعرّف المؤقت، وسيُحدَّث الترحيب وأيقونة الرتبة تلقائياً.
|
| 68 |
+
"""
|
| 69 |
+
return "زائر_" + secrets.token_hex(3)
|
| 70 |
+
|
| 71 |
+
# ------------------------------------------------------------
|
| 72 |
+
# ✅ (1) تحديث ملف status.json في الخزنة السحابية بعد كل عملية رفع
|
| 73 |
+
# ------------------------------------------------------------
|
| 74 |
+
def update_status_json(status_text: str):
|
| 75 |
+
try:
|
| 76 |
+
local_path = os.path.join("system", "status.json")
|
| 77 |
+
data = {
|
| 78 |
+
"status": status_text,
|
| 79 |
+
"updated": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 80 |
+
}
|
| 81 |
+
with open(local_path, "w", encoding="utf-8") as f:
|
| 82 |
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
| 83 |
+
|
| 84 |
+
api.upload_file(
|
| 85 |
+
path_or_fileobj=local_path,
|
| 86 |
+
path_in_repo=SYSTEM_STATUS_PATH,
|
| 87 |
+
repo_id=REPO_ID,
|
| 88 |
+
repo_type="dataset",
|
| 89 |
+
token=TOKEN,
|
| 90 |
+
)
|
| 91 |
+
except Exception as e:
|
| 92 |
+
log_error(f"STATUS UPDATE ERROR: {e}")
|
| 93 |
+
|
| 94 |
+
# ------------------------------------------------------------
|
| 95 |
+
# ✅ (4) إرسال تقرير من الواجهة إلى مكتب المدير عبر system/messages.json
|
| 96 |
+
# ------------------------------------------------------------
|
| 97 |
+
def send_report_to_manager(report_text: str):
|
| 98 |
+
try:
|
| 99 |
+
try:
|
| 100 |
+
local_existing = hf_hub_download(
|
| 101 |
+
repo_id=REPO_ID,
|
| 102 |
+
filename=MESSAGES_PATH,
|
| 103 |
+
repo_type="dataset",
|
| 104 |
+
token=TOKEN,
|
| 105 |
+
)
|
| 106 |
+
with open(local_existing, "r", encoding="utf-8") as f:
|
| 107 |
+
messages = json.load(f)
|
| 108 |
+
if not isinstance(messages, list):
|
| 109 |
+
messages = []
|
| 110 |
+
except Exception:
|
| 111 |
+
messages = []
|
| 112 |
+
|
| 113 |
+
messages.append({
|
| 114 |
+
"time": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
| 115 |
+
"from": "upload_interface",
|
| 116 |
+
"report": report_text,
|
| 117 |
+
})
|
| 118 |
+
|
| 119 |
+
local_path = os.path.join("system", "messages.json")
|
| 120 |
+
with open(local_path, "w", encoding="utf-8") as f:
|
| 121 |
+
json.dump(messages, f, ensure_ascii=False, indent=2)
|
| 122 |
+
|
| 123 |
+
api.upload_file(
|
| 124 |
+
path_or_fileobj=local_path,
|
| 125 |
+
path_in_repo=MESSAGES_PATH,
|
| 126 |
+
repo_id=REPO_ID,
|
| 127 |
+
repo_type="dataset",
|
| 128 |
+
token=TOKEN,
|
| 129 |
+
)
|
| 130 |
+
except Exception as e:
|
| 131 |
+
log_error(f"REPORT SEND ERROR: {e}")
|
| 132 |
+
|
| 133 |
+
# ------------------------------------------------------------
|
| 134 |
+
# ✅ (3) تحميل تفاصيل الاشتراك من system/about_app.json (يكتبها المدير)
|
| 135 |
+
# ------------------------------------------------------------
|
| 136 |
+
def load_about_app(rank: str = "free") -> str:
|
| 137 |
+
data = {}
|
| 138 |
+
try:
|
| 139 |
+
local_path = hf_hub_download(
|
| 140 |
+
repo_id=REPO_ID,
|
| 141 |
+
filename=ABOUT_APP_PATH,
|
| 142 |
+
repo_type="dataset",
|
| 143 |
+
token=TOKEN,
|
| 144 |
+
)
|
| 145 |
+
with open(local_path, "r", encoding="utf-8") as f:
|
| 146 |
+
data = json.load(f)
|
| 147 |
+
except Exception as e:
|
| 148 |
+
log_error(f"ABOUT APP READ ERROR: {e}")
|
| 149 |
+
|
| 150 |
+
if isinstance(data, dict):
|
| 151 |
+
section_text = (
|
| 152 |
+
data.get(rank)
|
| 153 |
+
or data.get("message")
|
| 154 |
+
or data.get("about")
|
| 155 |
+
or "لا تتوفر تفاصيل إضافية حالياً."
|
| 156 |
+
)
|
| 157 |
+
else:
|
| 158 |
+
section_text = "لا تتوفر تفاصيل إضافية حالياً."
|
| 159 |
+
|
| 160 |
+
badge = "🥇 عضوية VIP" if rank == "vip" else "🥈 عضوية مجانية (Free)"
|
| 161 |
+
|
| 162 |
+
html = f"""
|
| 163 |
+
<div role="dialog" aria-label="تفاصيل الاشتراك"
|
| 164 |
+
style="background:#ffffff; border:1px solid #ddd; border-radius:8px; padding:16px; margin-top:8px;">
|
| 165 |
+
<h3 style="margin-top:0;">{badge}</h3>
|
| 166 |
+
<p style="white-space:pre-line; margin:0;">{section_text}</p>
|
| 167 |
+
</div>
|
| 168 |
+
"""
|
| 169 |
+
return html
|
| 170 |
+
|
| 171 |
# ============================================================
|
| 172 |
# 2) رادار فحص القيود للمشترك المجاني (FREE)
|
| 173 |
# ============================================================
|
|
|
|
| 234 |
with open("./system/status.json", "r", encoding="utf-8") as f:
|
| 235 |
data = json.load(f)
|
| 236 |
|
| 237 |
+
status_text = data.get('status', 'غير معروف')
|
| 238 |
+
updated_text = data.get('updated', 'غير معروف')
|
| 239 |
+
|
| 240 |
+
# ✅ (2) مربع الحالة الآن متوافق مع قارئ الشاشة:
|
| 241 |
+
# role="status" + aria-live="polite" تجعل البرنامج يقرأ كل تحديث تلقائياً
|
| 242 |
html = f"""
|
| 243 |
+
<div role="status" aria-live="polite" aria-atomic="true"
|
| 244 |
+
style="font-size:16px; background-color: #f0f7ff; padding: 10px; border-radius: 5px; border: 1px solid #b3d7ff;">
|
| 245 |
+
<p style="margin:4px 0;"><b>📌 حالة نظام المعالجة الحالية:</b> <span>{status_text}</span></p>
|
| 246 |
+
<p style="margin:4px 0;"><b>🕒 آخر تحديث للسجل:</b> <span>{updated_text}</span></p>
|
| 247 |
</div>
|
| 248 |
"""
|
| 249 |
return html
|
| 250 |
except Exception as e:
|
| 251 |
log_error(f"STATUS READ ERROR: {e}")
|
| 252 |
+
return f"""<div role="status" aria-live="polite" style='color:orange;'>⚠️ لا يمكن قراءة ملف status.json حالياً — {e}</div>"""
|
| 253 |
|
| 254 |
# ============================================================
|
| 255 |
# 4) معالجة رفع الملفات مع دعم جميع البادئات
|
| 256 |
# ============================================================
|
| 257 |
|
| 258 |
+
def handle_upload(files, session,
|
| 259 |
+
enable_table, enable_handwriting,
|
| 260 |
+
enable_heavy_prep, enable_high_dpi,
|
| 261 |
enable_fast, enable_tashkeel):
|
| 262 |
+
if not files:
|
| 263 |
+
return "<div role='alert'>❌ يرجى اختيار ملفات للرفع</div>"
|
| 264 |
+
|
| 265 |
+
session = session or {}
|
| 266 |
+
# ⚠️ مؤقتاً: الاسم يولَّد تلقائياً للجلسة. سيُستبدل باسم المستخدم الحقيقي
|
| 267 |
+
# فور دمج "كود التعرف" (الاسم + البريد + بصمة الجهاز + الرتبة).
|
| 268 |
+
username = sanitize_filename((session.get("name") or generate_guest_name()).strip())
|
| 269 |
+
rank = session.get("rank", "free")
|
| 270 |
is_vip = (rank == "vip")
|
| 271 |
+
|
| 272 |
if not is_vip:
|
| 273 |
allowed, message = check_free_user_limits(username)
|
| 274 |
if not allowed:
|
| 275 |
+
return f"<div role='alert' style='color:red; font-weight:bold;'>{message}</div>"
|
| 276 |
|
| 277 |
success = 0
|
| 278 |
prefix = f"VIP_{username}_" if is_vip else f"{username}_"
|
|
|
|
| 283 |
file_size = os.path.getsize(file.name)
|
| 284 |
|
| 285 |
if not is_vip and file_size > 3 * 1024 * 1024:
|
| 286 |
+
return "<div role='alert' style='color:red;'>❌ خطأ: أحد الملفات يتجاوز حجمه 3 ميجابايت!</div>"
|
| 287 |
|
| 288 |
# بناء بادئة الخيارات
|
| 289 |
option_prefix = ""
|
|
|
|
| 322 |
except Exception as e:
|
| 323 |
log_error(f"UPLOAD ERROR: {file.name} — {e}")
|
| 324 |
|
| 325 |
+
# ✅ (1) كتابة "تم رفع الملف" في system/status.json على الخزنة السحابية
|
| 326 |
+
if success > 0:
|
| 327 |
+
update_status_json("تم رفع الملف" if success == 1 else f"تم رفع {success} ملفات")
|
| 328 |
+
|
| 329 |
+
# ✅ (4) إرسال تقرير بهذه العملية إلى مكتب المدير (system/messages.json)
|
| 330 |
+
send_report_to_manager(
|
| 331 |
+
f"المستخدم {username} ({'VIP' if is_vip else 'FREE'}) رفع {success} من {len(files)} ملف/ات."
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
return f"<div role='status' style='color:green; font-weight:bold;'>تم رفع {success} ملف/ات مع الخيارات المحددة ✔</div>"
|
| 335 |
|
| 336 |
# ============================================================
|
| 337 |
# 5) إدارة وقراءة ملفات Final والتحويل (باستخدام hf_hub_download)
|
|
|
|
| 390 |
except Exception as e:
|
| 391 |
return "⚠️ فشل إنشاء ZIP", None
|
| 392 |
|
| 393 |
+
# ============================================================
|
| 394 |
+
# 5.b) الجلسة الحالية: ترحيب بالاسم + أيقونة الرتبة
|
| 395 |
+
# (نقطة الدخول المستقبلية لـ "كود التعرف")
|
| 396 |
+
# ============================================================
|
| 397 |
+
|
| 398 |
+
def init_session():
|
| 399 |
+
"""
|
| 400 |
+
🧩 يُستدعى عند تحميل الصفحة. حالياً يولّد جلسة "زائر" مؤقتة بالرتبة free.
|
| 401 |
+
عندما يُضاف "كود التعرف" (اسم + بريد + بصمة جهاز + رتبة)، يكفي استبدال
|
| 402 |
+
منطق هذه الدالة بنتيجة ذلك الكود، وسيُحدَّث الترحيب + الأيقونة + الشاشة
|
| 403 |
+
المعروضة تلقائياً بحسب الرتبة (vip / free) دون أي تغيير آخر في الواجهة.
|
| 404 |
+
"""
|
| 405 |
+
session = {"name": generate_guest_name(), "email": "", "device_id": "", "rank": "free"}
|
| 406 |
+
welcome_html = f"<p role='status' style='font-size:16px; margin:0;'>👋 مرحباً بك، <b>{session['name']}</b></p>"
|
| 407 |
+
icon_label = "🥇 VIP" if session["rank"] == "vip" else "🥈 FREE"
|
| 408 |
+
return session, welcome_html, icon_label
|
| 409 |
+
|
| 410 |
+
def toggle_subscription_panel(session, is_visible):
|
| 411 |
+
"""فتح/إغلاق شاشة تفاصيل الاشتراك المرتبطة بـ system/about_app.json"""
|
| 412 |
+
new_visible = not is_visible
|
| 413 |
+
if new_visible:
|
| 414 |
+
rank = (session or {}).get("rank", "free")
|
| 415 |
+
return gr.update(value=load_about_app(rank), visible=True), new_visible
|
| 416 |
+
return gr.update(visible=False), new_visible
|
| 417 |
+
|
| 418 |
# ============================================================
|
| 419 |
# 6) واجهات Gradio مع جميع الخيارات
|
| 420 |
# ============================================================
|
|
|
|
| 422 |
with gr.Blocks(title="📤 بوابة الرفع") as upload_page:
|
| 423 |
gr.Markdown("<h1 style='text-align:center;'>📤 بوابة الرفع ودورة حياة الملفات</h1>")
|
| 424 |
|
| 425 |
+
# ✅ (5) أُزيل مربع "اسم المستخدم" التجريبي. تُولَّد الجلسة تلقائياً وتُستبدل
|
| 426 |
+
# مستقبلاً ببيانات "كود التعرف" الحقيقية.
|
| 427 |
+
user_session = gr.State({"name": "", "email": "", "device_id": "", "rank": "free"})
|
| 428 |
+
panel_visible_state = gr.State(False)
|
| 429 |
+
|
| 430 |
+
# ✅ (3) بدل خيار vip/free النصي: ترحيب بالاسم + أيقونة ذهبية/فضية للرتبة
|
| 431 |
+
with gr.Row():
|
| 432 |
+
welcome_box = gr.HTML("<p role='status'>👋 جارٍ تجهيز جلستك...</p>")
|
| 433 |
+
rank_icon_btn = gr.Button("🥈 FREE", elem_id="rank_icon_btn", scale=0, min_width=110)
|
| 434 |
+
|
| 435 |
+
# الشاشة العامة لتفاصيل الاشتراك (مرتبطة بـ system/about_app.json الذي يكتبه المدير)
|
| 436 |
+
subscription_panel = gr.HTML(visible=False)
|
| 437 |
|
| 438 |
uploader = gr.File(label="اختر ملفاتك", file_count="multiple")
|
| 439 |
|
|
|
|
| 466 |
status_box = gr.HTML("<div>بانتظار رفع الملفات...</div>")
|
| 467 |
|
| 468 |
gr.Markdown("### 📜 حالة التجميع والمعالجة الفورية")
|
| 469 |
+
system_status = gr.HTML("<div role='status' aria-live='polite'>بانتظار تحديث الحالة من system/status.json...</div>")
|
| 470 |
+
|
| 471 |
+
# تجهيز الجلسة (الترحيب + أيقونة الرتبة) عند فتح الصفحة
|
| 472 |
+
upload_page.load(init_session, None, [user_session, welcome_box, rank_icon_btn])
|
| 473 |
|
| 474 |
+
# فتح/إغلاق شاشة تفاصيل الاشتراك عند الضغط على أيقونة الرتبة
|
| 475 |
+
rank_icon_btn.click(
|
| 476 |
+
fn=toggle_subscription_panel,
|
| 477 |
+
inputs=[user_session, panel_visible_state],
|
| 478 |
+
outputs=[subscription_panel, panel_visible_state]
|
| 479 |
+
)
|
| 480 |
+
|
| 481 |
+
# ربط زر الرفع بالدالة المعدلة (تعتمد على الجلسة بدل مربع الاسم/الرتبة)
|
| 482 |
upload_btn.click(
|
| 483 |
fn=handle_upload,
|
| 484 |
+
inputs=[uploader, user_session,
|
| 485 |
enable_table, enable_handwriting,
|
| 486 |
enable_heavy_prep, enable_high_dpi,
|
| 487 |
enable_fast, enable_tashkeel],
|
| 488 |
outputs=status_box
|
| 489 |
+
).then(read_status_json, None, system_status)
|
| 490 |
|
| 491 |
status_timer = gr.Timer(3.0)
|
| 492 |
status_timer.tick(read_status_json, None, system_status)
|
| 493 |
|
| 494 |
+
# ------------------- قسم ملفاتي المُصحَّح -------------------
|
| 495 |
with gr.Blocks(title="📁 ملفاتي") as files_page:
|
| 496 |
gr.Markdown("<h1 style='text-align:center;'>📁 الملفات الجاهزة للتحويل</h1>")
|
| 497 |
files_list = gr.Dropdown(label="اختر ملفاً من Final_Arabic_Files", choices=[], interactive=True)
|
|
|
|
| 535 |
app = gr.TabbedInterface([upload_page, files_page], ["📤 الرفع المباشر", "📁 ملفاتي"])
|
| 536 |
|
| 537 |
if __name__ == "__main__":
|
| 538 |
+
app.launch(server_name="0.0.0.0", server_port=7860)
|