Files changed (1) hide show
  1. app.py +214 -96
app.py CHANGED
@@ -1,100 +1,218 @@
 
 
 
 
 
 
1
  import os
2
- import json
3
  import re
4
- from datetime import datetime
5
- from fastapi import FastAPI
6
- from pydantic import BaseModel
7
-
8
- app = FastAPI()
9
-
10
- # ============================
11
- # 1) مجلدات النظام
12
- # ============================
13
- BASE_DIR = "Final_Arabic_Files"
14
- STATUS_FILE = "system/status.json"
15
- MESSAGES_FILE = "system/messages.json"
16
-
17
- os.makedirs(BASE_DIR, exist_ok=True)
18
- os.makedirs("system", exist_ok=True)
19
-
20
- # ============================
21
- # 2) نموذج استقبال JSON
22
- # ============================
23
- class OCRPayload(BaseModel):
24
- text: str
25
- username: str
26
- rank: str | None = None # ← إصلاح المشكلة هنا
27
-
28
- # ============================
29
- # 3) دوال مساعدة
30
- # ============================
31
- def clean_text(text):
32
- text = re.sub(r'[^\u0600-\u06FF0-9\s.,!?؛:()\-]', '', text)
33
- text = re.sub(r'\s+', ' ', text).strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  return text
35
 
36
- def save_status(username, filename):
37
- status = {
38
- "username": username,
39
- "filename": filename,
40
- "timestamp": datetime.now().isoformat()
41
- }
42
- with open(STATUS_FILE, "w", encoding="utf-8") as f:
43
- json.dump(status, f, ensure_ascii=False, indent=2)
44
-
45
- def save_message(username, text):
46
- entry = {
47
- "username": username,
48
- "text": text,
49
- "timestamp": datetime.now().isoformat()
50
- }
51
-
52
- if not os.path.exists(MESSAGES_FILE):
53
- with open(MESSAGES_FILE, "w", encoding="utf-8") as f:
54
- json.dump([entry], f, ensure_ascii=False, indent=2)
55
- return
56
-
57
- with open(MESSAGES_FILE, "r", encoding="utf-8") as f:
58
- data = json.load(f)
59
-
60
- data.append(entry)
61
-
62
- with open(MESSAGES_FILE, "w", encoding="utf-8") as f:
63
- json.dump(data, f, ensure_ascii=False, indent=2)
64
-
65
- # ============================
66
- # 4) نقطة استقبال الـ OCR
67
- # ============================
68
- @app.post("/receive_ocr")
69
- def receive_ocr(payload: OCRPayload):
70
-
71
- username = payload.username.strip()
72
- text = payload.text.strip()
73
-
74
- # تنظيف النص
75
- cleaned = clean_text(text)
76
-
77
- # إنشاء مجلد المستخدم
78
- user_dir = os.path.join(BASE_DIR, username)
79
- os.makedirs(user_dir, exist_ok=True)
80
-
81
- # اسم الملف النهائي
82
- filename = f"{username}_document.txt"
83
- filepath = os.path.join(user_dir, filename)
84
-
85
- # حفظ الملف yuh
86
- with open(filepath, "w", encoding="utf-8") as f:
87
- f.write(cleaned)
88
-
89
- # تحديث status.json
90
- save_status(username, filename)
91
-
92
- # تحديث messages.json
93
- save_message(username, cleaned)
94
-
95
- return {
96
- "status": "success",
97
- "username": username,
98
- "file": filename,
99
- "saved_to": filepath
100
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ """
4
+ Arabic Golden Engine – Pro Lite Edition (Stable)
5
+ """
6
+
7
  import os
 
8
  import re
9
+ import queue
10
+ import threading
11
+ import time
12
+ import logging
13
+ import tempfile
14
+ import shutil
15
+ import json
16
+ import pandas as pd
17
+ import gradio as gr
18
+ from huggingface_hub import HfApi
19
+ from fastapi import FastAPI, Request
20
+ from pyarabic.araby import strip_tashkeel, normalize_ligature
21
+
22
+ os.environ["TOKENIZERS_PARALLELISM"] = "false"
23
+
24
+ # ========== إعدادات المستودع ==========
25
+ REPO_ID = "Asem75/aiocr_asistant"
26
+ FINAL_DIR = "Final_Arabic_Files"
27
+ EXCEL_DIR = "editor_sync"
28
+
29
+ LOCAL_FREE_TXT_DIR = "./free_processed_txt_queue"
30
+ LOCAL_FREE_XLS_DIR = "./free_processed_xls_queue"
31
+ os.makedirs(LOCAL_FREE_TXT_DIR, exist_ok=True)
32
+ os.makedirs(LOCAL_FREE_XLS_DIR, exist_ok=True)
33
+
34
+ HF_TOKEN = os.environ.get("AIOCR_KEY")
35
+ api = HfApi(token=HF_TOKEN.strip().split()[0]) if HF_TOKEN else None
36
+
37
+ logging.basicConfig(level=logging.INFO)
38
+ logger = logging.getLogger("Golden_Engine")
39
+
40
+ # ========== طابور المعالجة ==========
41
+ ARABIC_ENGINE_QUEUE = queue.Queue()
42
+ latest_processed_text = ""
43
+
44
+ # ========== نسخة Pro Lite — دوال التصحيح ==========
45
+ COMMON_AR = set("""
46
+ الله هذا ذلك لكن لأن فإن كان تكون الذين الذي التي عندما حيث بينما أيضا جدا فقط بين حتى عليه عليها لديهم لديه منها منه فيها فيه منهم نحن أنت هو هي هم هن ثم بعد قبل خلال بسبب بدون حول تحت فوق ضد عبر نحو عند الى إلى على عن مع من ما ماذا لماذا كيف متى أين هنا هناك نعم لا ربما قد لقد إن أن إذا إذ
47
+ """.split())
48
+
49
+ PHONETIC_MAP = {
50
+ "هاذا": "هذا",
51
+ "هده": "هذه",
52
+ "دالك": "ذلك",
53
+ "لأنو": "لأنه",
54
+ "علشان": "لأن",
55
+ }
56
+
57
+ VISUAL_MAP = {
58
+ "اﻟ": "ال",
59
+ "ى": "ي",
60
+ "ئ": "ي",
61
+ "ؤ": "و",
62
+ "ة": "ه",
63
+ "اللا": "لا",
64
+ }
65
+
66
+ def pro_fix_word(word):
67
+ base = strip_tashkeel(normalize_ligature(word))
68
+
69
+ if base in COMMON_AR:
70
+ return base
71
+
72
+ if base in PHONETIC_MAP:
73
+ return PHONETIC_MAP[base]
74
+
75
+ for k, v in VISUAL_MAP.items():
76
+ if k in base:
77
+ return base.replace(k, v)
78
+
79
+ base2 = re.sub(r"(.)\1{2,}", r"\1\1", base)
80
+ return base2
81
+
82
+ def pro_lite_disambiguate(text):
83
+ words = text.split()
84
+ fixed = [pro_fix_word(w) for w in words]
85
+ return " ".join(fixed)
86
+
87
+ # ========== تنظيف + ترقيم + تنسيق ==========
88
+ def mechanical_clean(text: str) -> str:
89
+ text = re.sub(r"--- صفحة \d+ \(OCR\) ---", "", text)
90
+ text = re.sub(r"Page \d+", "", text)
91
+ text = re.sub(r"\s+", " ", text)
92
+ return text.strip()
93
+
94
+ def insert_punctuation(text: str) -> str:
95
+ text = re.sub(r'\b(أولاً|ثانياً|ثالثاً|ملاحظة|تنبيه|مثال)\b\s*', r'\1: ', text)
96
+ return text
97
+
98
+ def final_format(text: str) -> str:
99
+ lines = []
100
+ for chunk in text.split("\n"):
101
+ chunk = chunk.strip()
102
+ if not chunk:
103
+ continue
104
+ if not chunk.endswith((".", "،", "؟", "!", "؛", ":")):
105
+ chunk += "."
106
+ lines.append(chunk)
107
+ return "\n\n".join(lines)
108
+
109
+ def process_ocr_text(raw_text: str) -> str:
110
+ if not raw_text.strip():
111
+ return ""
112
+ text = mechanical_clean(raw_text)
113
+ text = pro_lite_disambiguate(text)
114
+ text = insert_punctuation(text)
115
+ text = final_format(text)
116
  return text
117
 
118
+ # ========== Excel ==========
119
+ def generate_excel_grid(text: str) -> str:
120
+ words = [w.strip() for w in text.split() if w.strip()]
121
+ df = pd.DataFrame(words, columns=["الكلمات المصححة"])
122
+ tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx")
123
+ df.to_excel(tmp.name, index=False, engine='openpyxl')
124
+ return tmp.name
125
+
126
+ # ========== حلقة المعالجة ==========
127
+ def in_memory_receiver_loop():
128
+ global latest_processed_text
129
+ logger.info("🚀 Pro Lite Engine Ready...")
130
+
131
+ while True:
132
+ try:
133
+ try:
134
+ raw_text, metadata = ARABIC_ENGINE_QUEUE.get(timeout=1)
135
+ except queue.Empty:
136
+ continue
137
+
138
+ source = metadata.get("source", "unknown")
139
+ rank = metadata.get("rank", "free")
140
+ is_vip = (rank == "vip")
141
+
142
+ clean_name = os.path.splitext(os.path.basename(source))[0]
143
+ txt_filename = f"{clean_name}.txt"
144
+ xls_filename = f"{clean_name}.xlsx"
145
+
146
+ logger.info("📥 معالجة: %s (%s)", txt_filename, rank)
147
+
148
+ final_output = process_ocr_text(raw_text)
149
+ latest_processed_text = final_output
150
+ excel_path = generate_excel_grid(final_output)
151
+
152
+ if api:
153
+ if is_vip:
154
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".txt") as tmp_txt:
155
+ tmp_txt.write(final_output.encode("utf-8"))
156
+ tmp_txt_path = tmp_txt.name
157
+
158
+ api.upload_file(
159
+ path_or_fileobj=tmp_txt_path,
160
+ path_in_repo=f"{FINAL_DIR}/{txt_filename}",
161
+ repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN
162
+ )
163
+ os.unlink(tmp_txt_path)
164
+
165
+ api.upload_file(
166
+ path_or_fileobj=excel_path,
167
+ path_in_repo=f"{EXCEL_DIR}/{xls_filename}",
168
+ repo_id=REPO_ID, repo_type="dataset", token=HF_TOKEN
169
+ )
170
+
171
+ logger.info("⚡ VIP رفع مباشر: %s", txt_filename)
172
+
173
+ else:
174
+ shutil.copy2(excel_path, os.path.join(LOCAL_FREE_XLS_DIR, xls_filename))
175
+ with open(os.path.join(LOCAL_FREE_TXT_DIR, txt_filename), "w", encoding="utf-8") as f:
176
+ f.write(final_output)
177
+ logger.info("🐢 FREE تخزين مؤقت: %s", txt_filename)
178
+
179
+ if os.path.exists(excel_path):
180
+ os.unlink(excel_path)
181
+
182
+ ARABIC_ENGINE_QUEUE.task_done()
183
+
184
+ except Exception as e:
185
+ logger.error("خطأ: %s", e)
186
+ time.sleep(1)
187
+
188
+ # ========== استقبال الـ OCR ==========
189
+ def receive_from_ocr_space(raw_text: str, username: str, rank: str):
190
+ prefix = "VIP_" if rank == "vip" else ""
191
+ simulated_source = f"{prefix}{username}_document"
192
+ ARABIC_ENGINE_QUEUE.put((raw_text, {"source": simulated_source, "rank": rank}))
193
+ return f"تم استقبال النص من {username}."
194
+
195
+ # ========== FastAPI ==========
196
+ fastapi_app = FastAPI()
197
+
198
+ @fastapi_app.post("/receive_ocr")
199
+ async def receive_ocr(request: Request):
200
+ payload = await request.json()
201
+ raw_text = payload.get("text", "")
202
+ username = payload.get("username", "UnknownUser")
203
+ rank = payload.get("rank", "free")
204
+ return receive_from_ocr_space(raw_text, username, rank)
205
+
206
+ # ========== Gradio ==========
207
+ with gr.Blocks(title="Arabic Golden Engine – Pro Lite") as demo:
208
+ gr.Markdown("# ⚡ المعالج الذهبي – نسخة Pro Lite")
209
+ gr.Textbox(label="آخر نص", value=lambda: latest_processed_text, every=2)
210
+
211
+ # ========== تشغيل ==========
212
+ threading.Thread(target=in_memory_receiver_loop, daemon=True).start()
213
+
214
+ app = gr.mount_gradio_app(fastapi_app, demo, path="/")
215
+
216
+ if __name__ == "__main__":
217
+ import uvicorn
218
+ uvicorn.run(app, host="0.0.0.0", port=7860)