AIOCR_Result / app.py
Asem75's picture
Update app.py
44db0bf verified
Raw
History Blame Contribute Delete
16.6 kB
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, "<div class='error-box'>🔒 المتغير البيئي AIOCR_KEY غير موجود في إعدادات السبيس الحالي. يرجى إضافته في قسم Settings بالأحرف الكبيرة.</div>"
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"<p style='color:#ffd700; text-align:center; padding:10px;'>🗄️ الرادار متصل بالخزنة، ولكن المجلد الموحد '{FINAL_DIR}' فارغ حالياً ولا يحتوي ملفات مستخرجة.</p>"
return results, None
except Exception as e:
error_msg = str(e)
return None, f"<div class='error-box'>⚠️ الرادار لم يتمكن من مسح المستودع الخاص.<br>الخطأ المباشر: {error_msg}</div>"
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"""<html><head><meta charset="utf-8"><title>{filename}</title></head>
<body dir="rtl" style="font-family: Arial; padding:20px;">
<pre style="white-space: pre-wrap;">{text}</pre>
</body></html>"""
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'<html><head></head><body dir="rtl"><pre>{text}</pre></body></html>'
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 = '<div style="max-height:400px; overflow-y:auto;">'
for f in files_list:
safe = f.replace("'", "\\'")
html += f"""
<div style="cursor:pointer; padding:8px; border-bottom:1px solid #333; color:#ffd700;"
onclick="selectFile('{safe}')"
ondblclick="loadFile('{safe}')">
📄 {f}
</div>"""
html += '</div>'
html += """
<script>
function selectFile(filename) {
var inp = document.querySelector('#preview_filename textarea, #preview_filename input');
if(inp) { inp.value = filename; inp.dispatchEvent(new Event('input', {bubbles:true})); }
}
function loadFile(filename) {
var inp = document.querySelector('#load_filename textarea, #load_filename input');
if(inp) { inp.value = filename; inp.dispatchEvent(new Event('input', {bubbles:true}));
var btn = document.querySelector('#load_btn button');
if(btn) btn.click();
}
}
</script>
"""
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('<div class="warning-banner">📡 تم ربط الرادار بالخزنة بنظام فك تشفير المعرفات والمفاتيح السيادية الموحدة</div>')
with gr.Row():
with gr.Column(scale=1, elem_classes="sidebar"):
refresh_btn = gr.Button("🔄 تحديث ورصد الخزنة", size="sm")
archive_html = gr.HTML(value="<p style='color:#c8a44c; text-align:center;'>جاري قراءة الخزنة الرقمية الموحدة...</p>")
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"""
<div style="display:flex; justify-content:center; width:100%; margin-top:20px;">
<a href="{MAIN_PORTAL}" style="text-decoration:none; padding:12px 24px; background:#1a1a1a; color:#ffd700; border:2px solid #c8a44c; border-radius:8px; font-weight:bold;">📤 العودة لبوابة الرفع الرئيسية</a>
</div>
""")
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")