import gradio as gr import os import re import tempfile import zipfile from huggingface_hub import HfApi, hf_hub_download, login # مكتبات تحويل الصيغ from fpdf import FPDF from docx import Document from docx.shared import Pt from docx.enum.text import WD_ALIGN_PARAGRAPH import arabic_reshaper from bidi.algorithm import get_display import openpyxl try: from ebooklib import epub EPUB_AVAILABLE = True except ImportError: EPUB_AVAILABLE = False # ================================================================= # إعدادات النظام العامة – مطابقة كاملة 100% مع الطبقة الثانية # ================================================================= # جلب التوكن بالكبير تماماً كما هو معرف في السبيس الآخر ومعالجته فوراً TOKEN = os.getenv("AIOCR_KEY") if TOKEN: # تنظيف التوكن تلقائياً من أي زوائد أو أسطر خفية قد تنتج عن اللصق TOKEN = TOKEN.strip().split()[0] try: login(token=TOKEN) api = HfApi(token=TOKEN) except Exception as login_err: print(f"⚠️ تنبيه تسجيل الدخول: {login_err}") api = HfApi() else: api = HfApi() REPO_ID = "Asem75/aiocr_asistant" FINAL_DIR = "Final_Arabic_Files" MAIN_PORTAL = "https://huggingface.co/spaces/Asem75/aiocr" # تصميم موحد ونظيف بدون أي تداخل برمجي custom_css = """ body, .gradio-container { background-color: #0a0a0a !important; color: #e0c080 !important; } .gr-box, .gr-form, .gr-panel { background-color: #1a1a1a !important; border: 1px solid #c8a44c !important; } .gr-button { background-color: #2a2a2a !important; color: #e0c080 !important; border: 1px solid #c8a44c !important; } .gr-button:hover { background-color: #3d2e1a !important; } .gr-textbox, .gr-dropdown { background-color: #121212 !important; color: #e0c080 !important; border: 1px solid #c8a44c !important; } #golden-editor textarea { background-color: #121212 !important; color: #f5d78c !important; font-size: 16px; font-family: 'Courier New', monospace; line-height: 1.6; direction: rtl; } .warning-banner { background-color: #3d2e1a; color: #ffcc00; padding: 8px; border-radius: 4px; margin-bottom: 10px; text-align: center; font-weight: bold; } .error-box { background-color: #4a1a1a; color: #ff9999; padding: 10px; border: 1px solid #ff3333; border-radius: 4px; font-size: 13px; margin: 5px 0; } """ def clean_filename(fn: str) -> str: patterns = [r'^Final_Result_', r'^OCR_Result_', r'^Processed_', r'^Result_', r'^Final_'] for pat in patterns: fn = re.sub(pat, '', fn, flags=re.IGNORECASE) name, _ = os.path.splitext(fn) return name.strip() # ------------------------------- # الرادار الذكي الموحد لقراءة الملفات # ------------------------------- def list_archive_files_reporting(): if not TOKEN: return None, "
🔒 المتغير البيئي AIOCR_KEY غير موجود في إعدادات السبيس الحالي. يرجى إضافته في قسم Settings بالأحرف الكبيرة.
" try: # استخدام الكائن الموثق بـ TOKEN المماثل للطبقة الثانية files = api.list_repo_files(REPO_ID, repo_type="dataset") results = [] prefix_folder = FINAL_DIR + "/" for f in files: if f.endswith('/') or f.endswith('.gitkeep') or '.git' in f: continue if f.lower().startswith(prefix_folder.lower()): # قص اسم المجلد للحصول على اسم الملف الصافي results.append(f[len(prefix_folder):]) elif '/' not in f: results.append(f) results = sorted(list(set(results))) if not results: return [], f"

🗄️ الرادار متصل بالخزنة، ولكن المجلد الموحد '{FINAL_DIR}' فارغ حالياً ولا يحتوي ملفات مستخرجة.

" return results, None except Exception as e: error_msg = str(e) return None, f"
⚠️ الرادار لم يتمكن من مسح المستودع الخاص.
الخطأ المباشر: {error_msg}
" def get_preview(filename: str) -> str: if not filename or not TOKEN: return "" try: try: path = hf_hub_download(repo_id=REPO_ID, filename=f"{FINAL_DIR}/{filename}", repo_type="dataset", token=TOKEN) except: path = hf_hub_download(repo_id=REPO_ID, filename=filename, repo_type="dataset", token=TOKEN) with open(path, 'r', encoding='utf-8') as f: lines = [f.readline() for _ in range(5)] return "".join(lines) except Exception as e: return f"⚠️ خطأ في المعاينة: {e}" def load_file_content(filename: str): if not filename or not TOKEN: return "", "" try: try: path = hf_hub_download(repo_id=REPO_ID, filename=f"{FINAL_DIR}/{filename}", repo_type="dataset", token=TOKEN) except: path = hf_hub_download(repo_id=REPO_ID, filename=filename, repo_type="dataset", token=TOKEN) with open(path, 'r', encoding='utf-8') as f: content = f.read() return content, clean_filename(filename) except Exception as e: return f"⚠️ خطأ في تحميل الملف: {e}", filename # ---- أدوات معالجة النصوص المحررة ---- def clean_spaces(text): text = re.sub(r'[ \t]+', ' ', text) text = re.sub(r'\n\s*\n', '\n\n', text) return text.strip() def rtl_align(text): return '\u202B' + text + '\u202C' def convert_numerals(text): eastern = '٠١٢٣٤٥٦٧٨٩' western = '0123456789' return text.translate(str.maketrans(western, eastern)) # ---- محركات تحويل وتصدير المستندات ---- def convert_to_pdf(text, filename): try: reshaped = arabic_reshaper.reshape(text) bidi_text = get_display(reshaped) except Exception: bidi_text = text pdf = FPDF() pdf.add_page() font_path = "/usr/share/fonts/truetype/noto/NotoNaskhArabic-Regular.ttf" if not os.path.exists(font_path): font_path = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf" if os.path.exists(font_path): pdf.add_font("Arabic", "", font_path, uni=True) pdf.set_font("Arabic", size=12) else: pdf.set_font("Helvetica", size=12) pdf.set_auto_page_break(auto=True, margin=15) pdf.multi_cell(0, 10, bidi_text) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") pdf.output(tmp.name) return tmp.name def convert_to_docx(text, filename): doc = Document() for line in text.split('\n'): p = doc.add_paragraph() run = p.add_run(line) run.font.size = Pt(12) run.font.rtl = True p.alignment = WD_ALIGN_PARAGRAPH.RIGHT tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".docx") doc.save(tmp.name) return tmp.name def convert_to_rtf(text, filename): rtf = r"{\rtf1\ansi\ansicpg1256\deff0\nouicompat" rtf += r"{\fonttbl{\f0\fnil\fcharset0 Arial;}}" rtf += r"\viewkind4\uc1\pard\rtlpar\f0\fs24 " rtf += text.replace('\n', r'\par ') + r"}" tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".rtf") with open(tmp.name, 'w', encoding='utf-8') as f: f.write(rtf) return tmp.name def convert_to_html(text, filename): html = f"""{filename}
{text}
""" tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".html") with open(tmp.name, 'w', encoding='utf-8') as f: f.write(html) return tmp.name def convert_to_xlsx(text, filename): wb = openpyxl.Workbook() ws = wb.active ws.title = "AIOCR Output" for i, line in enumerate(text.split('\n'), start=1): ws.cell(row=i, column=1, value=line) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".xlsx") wb.save(tmp.name) return tmp.name def convert_to_epub(text, filename): if not EPUB_AVAILABLE: raise gr.Error("مكتبة ebooklib غير مثبتة.") book = epub.EpubBook() book.set_identifier('aiocr_output') book.set_title(filename) book.set_language('ar') chapter = epub.EpubHtml(title='Output', file_name='chap_1.xhtml', lang='ar') chapter.content = f'
{text}
' book.add_item(chapter) book.toc = [epub.Link('chap_1.xhtml', 'Output', 'output')] book.spine = [chapter] book.add_item(epub.EpubNcx()) book.add_item(epub.EpubNav()) tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".epub") epub.write_epub(tmp.name, book, {}) return tmp.name def convert_to_code(text, filename): tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".txt") with open(tmp.name, 'w', encoding='utf-8') as f: f.write(text) return tmp.name def render_interface(): files_list, error_html = list_archive_files_reporting() if error_html: return error_html html = '
' for f in files_list: safe = f.replace("'", "\\'") html += f"""
📄 {f}
""" html += '
' html += """ """ return html # ------------------------------- # واجهة مستخدم GRADIO المستقرة # ------------------------------- with gr.Blocks(css=custom_css, title="AIOCR_RESULT – المحرر الذهبي") as demo: current_file_state = gr.Textbox(visible=False) with gr.Column(visible=True) as main_ui: gr.HTML('
📡 تم ربط الرادار بالخزنة بنظام فك تشفير المعرفات والمفاتيح السيادية الموحدة
') with gr.Row(): with gr.Column(scale=1, elem_classes="sidebar"): refresh_btn = gr.Button("🔄 تحديث ورصد الخزنة", size="sm") archive_html = gr.HTML(value="

جاري قراءة الخزنة الرقمية الموحدة...

") preview_box = gr.Textbox(label="📋 معاينة سريعة", lines=6, interactive=False) with gr.Column(scale=3): editor = gr.Textbox( label="📜 شاشة المحرر الذهبي الرئيسي", lines=20, elem_id="golden-editor", placeholder="انقر نقراً مزدوجاً (Double Click) على أي ملف للفتحه هنا بمجرد ظهوره بالأرشيف..." ) with gr.Row(): copy_btn = gr.Button("📋 نسخ النص") clean_btn = gr.Button("🧹 تنظيف المسافات") rtl_btn = gr.Button("↩️ محاذاة RTL") numeral_btn = gr.Button("🔢 أرقام عربية") with gr.Row(): filename_input = gr.Textbox(label="📝 اسم الملف النهائي", value="", scale=3) format_dropdown = gr.Dropdown( choices=["PDF", "DOCX", "TXT", "RTF", "HTML", "EXCEL (XLSX)", "EPUB", "CODE"], value="PDF", label="📄 تنسيق التصدير" ) with gr.Row(): download_single_btn = gr.DownloadButton("📥 تحميل المستند", variant="primary") download_zip_btn = gr.DownloadButton("🗜️ تحميل الأرشيف (ZIP)", variant="secondary") with gr.Row(): gr.HTML(f"""
📤 العودة لبوابة الرفع الرئيسية
""") preview_filename = gr.Textbox(visible=False, elem_id="preview_filename") load_filename = gr.Textbox(visible=False, elem_id="load_filename") load_btn = gr.Button("تحميل", visible=False, elem_id="load_btn") timer = gr.Timer(12) demo.load(fn=render_interface, outputs=archive_html) refresh_btn.click(fn=render_interface, outputs=archive_html) timer.tick(fn=render_interface, outputs=archive_html) preview_filename.change(fn=get_preview, inputs=preview_filename, outputs=preview_box) def load_full(filename): text, clean_name = load_file_content(filename) return text, clean_name, filename load_btn.click(fn=load_full, inputs=load_filename, outputs=[editor, filename_input, current_file_state]) copy_btn.click( fn=None, inputs=None, outputs=None, js=""" () => { var txt = document.querySelector('#golden-editor textarea'); if(txt) navigator.clipboard.writeText(txt.value).then(() => alert('✔ تم نسخ النص!')); } """ ) clean_btn.click(fn=clean_spaces, inputs=editor, outputs=editor) rtl_btn.click(fn=rtl_align, inputs=editor, outputs=editor) numeral_btn.click(fn=convert_numerals, inputs=editor, outputs=editor) def handle_single_download(text, filename, fmt, current_file): if not text: raise gr.Error("لا يوجد نص.") format_map = { "PDF": (convert_to_pdf, ".pdf"), "DOCX": (convert_to_docx, ".docx"), "TXT": (lambda t, fn: tempfile.NamedTemporaryFile(delete=False, suffix=".txt").name, ".txt"), "RTF": (convert_to_rtf, ".rtf"), "HTML": (convert_to_html, ".html"), "EXCEL (XLSX)": (convert_to_xlsx, ".xlsx"), "EPUB": (convert_to_epub, ".epub"), "CODE": (convert_to_code, ".txt") } converter, ext = format_map.get(fmt, (lambda t, fn: tempfile.NamedTemporaryFile(delete=False, suffix=".txt").name, ".txt")) save_name = filename.strip() + ext if fmt == "TXT": tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".txt") with open(tmp.name, 'w', encoding='utf-8') as f: f.write(text) filepath = tmp.name else: filepath = converter(text, save_name) return filepath download_single_btn.click(fn=handle_single_download, inputs=[editor, filename_input, format_dropdown, current_file_state], outputs=download_single_btn) def handle_zip_download(): files_list, _ = list_archive_files_reporting() if not files_list: raise gr.Error("لا توجد ملفات متوفرة.") zip_buffer = tempfile.NamedTemporaryFile(delete=False, suffix=".zip") with zipfile.ZipFile(zip_buffer, 'w') as zf: for fn in files_list: try: path = hf_hub_download(repo_id=REPO_ID, filename=f"{FINAL_DIR}/{fn}", repo_type="dataset", token=TOKEN) zf.write(path, arcname=fn) except: continue return zip_buffer.name download_zip_btn.click(fn=handle_zip_download, outputs=download_zip_btn) if __name__ == "__main__": demo.launch(server_name="0.0.0.0")