Asem75 commited on
Commit
16d7dac
·
verified ·
1 Parent(s): d0c8b7b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +200 -24
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 style="font-size:16px; background-color: #f0f7ff; padding: 10px; border-radius: 5px; border: 1px solid #b3d7ff;">
127
- <b>📌 حالة نظام المعالجة الحالية:</b> {data.get('status','غير معروف')}<br>
128
- <b>🕒 آخر تحديث للسجل:</b> {data.get('updated','غير معروف')}
 
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, username, rank,
141
- enable_table, enable_handwriting,
142
- enable_heavy_prep, enable_high_dpi,
143
  enable_fast, enable_tashkeel):
144
- if not files or not username:
145
- return "<div>❌ يرجى اختيار ملفات وكتابة اسم المستخدم بشكل صحيح</div>"
146
-
147
- username = sanitize_filename(username.strip())
 
 
 
 
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
- return f"<div style='color:green; font-weight:bold;'>تم رفع {success} ملف/ات مع الخيارات المحددة ✔</div>"
 
 
 
 
 
 
 
 
 
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
- username_input = gr.Textbox(label="👤 اسم المستخدم", value="Divid")
270
- rank_input = gr.Radio(label="🏅 الرتبة", choices=["free", "vip"], value="free")
 
 
 
 
 
 
 
 
 
 
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, username_input, rank_input,
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)