OmniFile-Processor / gui_app.py
Dr. Abdulmalek
deploy: OmniFile AI Processor v4.3.0
900df0b
"""
الواجهة الرسومية المتكاملة لـ OmniFile Processor
=====================================================
واجهة Tkinter احترافية تجمع جميع وحدات المشروع في مكان واحد.
التبويبات:
1. المعالجة (Processing) — استخراج النصوص من الملفات
2. التدقيق والاعتماد (Review) — مراجعة النصوص واعتمادها للتدريب
3. البحث الشامل (Search) — البحث في الأرشيف
4. الإحصائيات (Statistics) — عرض إحصائيات المشروع
5. الإعدادات (Settings) — تهيئة المعالجة
المتطلبات:
python -c "import tkinter" # Tkinter مدمج مع بايثون
pip install paddleocr paddlepaddle # لمحرك OCR (اختياري)
الاستخدام:
python gui_app.py
"""
import os
import sys
import json
import threading
import queue
import tkinter as tk
from tkinter import (
filedialog, messagebox, scrolledtext, ttk,
Frame, Label, Button, Entry, Text, StringVar,
BooleanVar, DoubleVar, PanedWindow, Menu
)
from datetime import datetime
from typing import Optional, Dict, Any, List, Callable
# إضافة مسار المشروع
PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
if PROJECT_ROOT not in sys.path:
sys.path.insert(0, PROJECT_ROOT)
class OmniFileGUI:
"""
الواجهة الرسومية الرئيسية لـ OmniFile Processor.
تجمع بين:
- نظام بصمة الملفات (FileFingerprintManager)
- مصنف المحتوى الطبي (MedicalClassifier)
- محرك OCR (EasyOCR / PaddleOCR)
- التدقيق اللغوي (LanguageCorrector)
- مولد بيانات التدريب (DatasetGenerator)
- محرك البحث الشامل (SearchEngine)
"""
# الألوان (نظام ليلي مريح)
COLORS = {
"bg_dark": "#1e1e2e",
"bg_medium": "#2d2d44",
"bg_light": "#3d3d5c",
"bg_input": "#454566",
"fg_primary": "#e0e0e0",
"fg_secondary": "#a0a0b8",
"accent_blue": "#5b8def",
"accent_green": "#4caf50",
"accent_orange": "#ff9800",
"accent_red": "#f44336",
"accent_purple": "#9c27b0",
"border": "#555577",
"success": "#81c784",
"warning": "#ffb74d",
"error": "#e57373",
}
def __init__(self, root: tk.Tk):
"""
تهيئة الواجهة الرسومية.
Args:
root: نافذة Tkinter الرئيسية
"""
self.root = root
self.root.title("OmniFile AI Processor v4.2.0 — معالج الملفات الشامل")
self.root.geometry("1000x700")
self.root.minsize(800, 600)
self.root.configure(bg=self.COLORS["bg_dark"])
# متغيرات الحالة
self.selected_folder = StringVar(value="")
self.output_folder = StringVar(value="")
self.search_query = StringVar(value="")
self.status_var = StringVar(value="جاهز")
self.progress_var = DoubleVar(value=0)
self.confidence_threshold = DoubleVar(value=0.70)
self.auto_watch_var = BooleanVar(value=False)
# قائمة انتظار الرسائل من الخيوط
self._msg_queue: queue.Queue = queue.Queue()
# المكونات الداخلية (تُحمّل كسول)
self._fingerprint_mgr = None
self._classifier = None
self._corrector = None
self._dataset_gen = None
self._search_engine = None
self._ocr_engine = None
self._watchdog = None
# بناء الواجهة
self._setup_styles()
self._create_menu()
self._create_header()
self._create_notebook()
self._create_status_bar()
self._check_initialization()
# بدء مراقبة الرسائل
self._process_messages()
# ==================================================================
# إعداد الأنماط والألوان
# ==================================================================
def _setup_styles(self):
"""إعداد أنماط ttk."""
style = ttk.Style()
style.theme_use("clam")
style.configure("Dark.TFrame", background=self.COLORS["bg_dark"])
style.configure("Medium.TFrame", background=self.COLORS["bg_medium"])
style.configure("Dark.TNotebook", background=self.COLORS["bg_dark"])
style.configure("Dark.TNotebook.Tab",
background=self.COLORS["bg_medium"],
foreground=self.COLORS["fg_primary"],
padding=[12, 6])
style.map("Dark.TNotebook.Tab",
background=[("selected", self.COLORS["bg_light"])],
foreground=[("selected", self.COLORS["accent_blue"])])
style.configure("Dark.TLabel",
background=self.COLORS["bg_dark"],
foreground=self.COLORS["fg_primary"])
style.configure("Header.TLabel",
background=self.COLORS["bg_medium"],
foreground=self.COLORS["accent_blue"],
font=("Arial", 14, "bold"))
style.configure("Status.TLabel",
background=self.COLORS["bg_medium"],
foreground=self.COLORS["fg_secondary"])
style.configure("Action.TButton",
background=self.COLORS["accent_blue"],
foreground="white",
font=("Arial", 10, "bold"),
padding=[16, 8])
style.map("Action.TButton",
background=[("active", "#4a7de0")])
style.configure("Success.TButton",
background=self.COLORS["accent_green"],
foreground="white",
font=("Arial", 10, "bold"),
padding=[16, 8])
style.configure("Warning.TButton",
background=self.COLORS["accent_orange"],
foreground="white",
font=("Arial", 10, "bold"),
padding=[16, 8])
style.configure("Dark.TProgressbar",
background=self.COLORS["accent_blue"],
troughcolor=self.COLORS["bg_light"])
style.configure("Dark.TCheckbutton",
background=self.COLORS["bg_dark"],
foreground=self.COLORS["fg_primary"])
def _create_menu(self):
"""إنشاء شريط القوائم."""
menubar = Menu(self.root, bg=self.COLORS["bg_medium"],
fg=self.COLORS["fg_primary"])
file_menu = Menu(menubar, tearoff=0)
file_menu.add_command(label="اختيار مجلد الإدخال", command=self._select_input_folder)
file_menu.add_command(label="اختيار مجلد المخرجات", command=self._select_output_folder)
file_menu.add_separator()
file_menu.add_command(label="خروج", command=self.root.quit)
menubar.add_cascade(label="ملف", menu=file_menu)
tools_menu = Menu(menubar, tearoff=0)
tools_menu.add_command(label="كشف الملفات المكررة", command=self._find_duplicates)
tools_menu.add_command(label="تصدير الإحصائيات", command=self._export_stats)
tools_menu.add_command(label="تنظيف السجلات القديمة", command=self._cleanup_records)
menubar.add_cascade(label="أدوات", menu=tools_menu)
help_menu = Menu(menubar, tearoff=0)
help_menu.add_command(label="عن البرنامج", command=self._show_about)
menubar.add_cascade(label="مساعدة", menu=help_menu)
self.root.config(menu=menubar)
def _create_header(self):
"""إنشاء شريط الرأس."""
header = Frame(self.root, bg=self.COLORS["bg_medium"], height=50)
header.pack(fill=tk.X, padx=0, pady=0)
header.pack_propagate(False)
# العنوان
title_label = Label(
header, text="OmniFile AI Processor v4.2.0",
font=("Arial", 16, "bold"),
bg=self.COLORS["bg_medium"],
fg=self.COLORS["accent_blue"],
)
title_label.pack(side=tk.LEFT, padx=15, pady=10)
# حالة المحركات
self._engine_status = Label(
header, text="جاري التحميل...",
font=("Arial", 9),
bg=self.COLORS["bg_medium"],
fg=self.COLORS["fg_secondary"],
)
self._engine_status.pack(side=tk.RIGHT, padx=15, pady=10)
def _create_notebook(self):
"""إنشاء دفتر التبويبات الرئيسي."""
self.notebook = ttk.Notebook(self.root, style="Dark.TNotebook")
self.notebook.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)
# تبويب المعالجة
self._create_processing_tab()
# تبويب التدقيق والاعتماد
self._create_review_tab()
# تبويب البحث
self._create_search_tab()
# تبويب الإحصائيات
self._create_stats_tab()
def _create_status_bar(self):
"""إنشاء شريط الحالة."""
status_bar = Frame(self.root, bg=self.COLORS["bg_medium"], height=30)
status_bar.pack(fill=tk.X, side=tk.BOTTOM, padx=0, pady=0)
status_bar.pack_propagate(False)
self.status_label = Label(
status_bar, textvariable=self.status_var,
font=("Arial", 9),
bg=self.COLORS["bg_medium"],
fg=self.COLORS["fg_secondary"],
anchor=tk.W,
)
self.status_label.pack(fill=tk.X, padx=10, pady=5)
# شريط التقدم
self.progress_bar = ttk.Progressbar(
status_bar, variable=self.progress_var,
maximum=100, style="Dark.TProgressbar",
)
self.progress_bar.place(relx=0.8, rely=0.5, relwidth=0.18, anchor=tk.CENTER)
# ==================================================================
# تبويب المعالجة
# ==================================================================
def _create_processing_tab(self):
"""إنشاء تبويب المعالجة."""
tab = Frame(self.notebook, bg=self.COLORS["bg_dark"])
self.notebook.add(tab, text=" المعالجة ")
# الجزء العلوي — إعدادات
top_frame = Frame(tab, bg=self.COLORS["bg_dark"])
top_frame.pack(fill=tk.X, padx=10, pady=10)
# اختيار المجلد
folder_frame = Frame(top_frame, bg=self.COLORS["bg_dark"])
folder_frame.pack(fill=tk.X, pady=5)
Label(folder_frame, text="مجلد الإدخال:",
bg=self.COLORS["bg_dark"], fg=self.COLORS["fg_primary"],
font=("Arial", 10)).pack(side=tk.LEFT, padx=(0, 5))
self.folder_entry = Entry(
folder_frame, textvariable=self.selected_folder,
bg=self.COLORS["bg_input"], fg=self.COLORS["fg_primary"],
insertbackground="white", font=("Arial", 10),
width=50,
)
self.folder_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
Button(
folder_frame, text="استعراض", command=self._select_input_folder,
bg=self.COLORS["bg_light"], fg=self.COLORS["fg_primary"],
relief=tk.FLAT, padx=15
).pack(side=tk.LEFT, padx=5)
# أزرار التحكم
btn_frame = Frame(top_frame, bg=self.COLORS["bg_dark"])
btn_frame.pack(fill=tk.X, pady=10)
self._start_btn = Button(
btn_frame, text="بدء المعالجة",
command=self._start_processing,
bg=self.COLORS["accent_blue"], fg="white",
font=("Arial", 11, "bold"), relief=tk.FLAT,
padx=20, pady=8, cursor="hand2",
)
self._start_btn.pack(side=tk.LEFT, padx=5)
self._stop_btn = Button(
btn_frame, text="إيقاف",
command=self._stop_processing,
bg=self.COLORS["accent_red"], fg="white",
font=("Arial", 11, "bold"), relief=tk.FLAT,
padx=20, pady=8, cursor="hand2",
state=tk.DISABLED,
)
self._stop_btn.pack(side=tk.LEFT, padx=5)
# مراقبة تلقائية
chk = tk.Checkbutton(
btn_frame, text="مراقبة تلقائية (Watchdog)",
variable=self.auto_watch_var,
command=self._toggle_watchdog,
bg=self.COLORS["bg_dark"], fg=self.COLORS["fg_primary"],
selectcolor=self.COLORS["bg_light"],
activebackground=self.COLORS["bg_dark"],
activeforeground=self.COLORS["fg_primary"],
font=("Arial", 10),
)
chk.pack(side=tk.LEFT, padx=20)
# منطقة السجلات
log_label = Label(tab, text="سجل العمليات:",
bg=self.COLORS["bg_dark"], fg=self.COLORS["fg_secondary"],
font=("Arial", 10, "bold"))
log_label.pack(anchor=tk.W, padx=10)
self.log_area = scrolledtext.ScrolledText(
tab, bg=self.COLORS["bg_input"], fg=self.COLORS["fg_primary"],
font=("Courier New", 9), wrap=tk.WORD,
insertbackground="white", height=20,
)
self.log_area.pack(fill=tk.BOTH, expand=True, padx=10, pady=(5, 10))
# تكوين ألوان النص
self.log_area.tag_configure("success", foreground=self.COLORS["success"])
self.log_area.tag_configure("warning", foreground=self.COLORS["warning"])
self.log_area.tag_configure("error", foreground=self.COLORS["error"])
self.log_area.tag_configure("info", foreground=self.COLORS["accent_blue"])
self.log_area.tag_configure("header", foreground=self.COLORS["accent_purple"],
font=("Courier New", 10, "bold"))
# ==================================================================
# تبويب التدقيق والاعتماد
# ==================================================================
def _create_review_tab(self):
"""إنشاء تبويب التدقيق والاعتماد."""
tab = Frame(self.notebook, bg=self.COLORS["bg_dark"])
self.notebook.add(tab, text=" التدقيق والاعتماد ")
# منطقة النص للمراجعة
review_frame = Frame(tab, bg=self.COLORS["bg_dark"])
review_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
# النص الأصلي
Label(review_frame, text="النص الأصلي (OCR):",
bg=self.COLORS["bg_dark"], fg=self.COLORS["fg_secondary"],
font=("Arial", 10, "bold")).pack(anchor=tk.W)
self.original_text = scrolledtext.ScrolledText(
review_frame, bg=self.COLORS["bg_input"], fg=self.COLORS["fg_primary"],
font=("Arial", 11), wrap=tk.WORD, height=8,
insertbackground="white",
)
self.original_text.pack(fill=tk.X, pady=(5, 10))
# أزرار التدقيق
review_btns = Frame(review_frame, bg=self.COLORS["bg_dark"])
review_btns.pack(fill=tk.X, pady=5)
Button(
review_btns, text="تدقيق لغوي",
command=self._run_spell_check,
bg=self.COLORS["accent_blue"], fg="white",
font=("Arial", 10, "bold"), relief=tk.FLAT,
padx=15, pady=5, cursor="hand2",
).pack(side=tk.LEFT, padx=5)
Button(
review_btns, text="تصنيف المحتوى",
command=self._run_classification,
bg=self.COLORS["accent_purple"], fg="white",
font=("Arial", 10, "bold"), relief=tk.FLAT,
padx=15, pady=5, cursor="hand2",
).pack(side=tk.LEFT, padx=5)
# النتيجة
Label(review_frame, text="النص المصحح:",
bg=self.COLORS["bg_dark"], fg=self.COLORS["fg_secondary"],
font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 0))
self.corrected_text = scrolledtext.ScrolledText(
review_frame, bg=self.COLORS["bg_input"], fg=self.COLORS["success"],
font=("Arial", 11), wrap=tk.WORD, height=8,
insertbackground="white",
)
self.corrected_text.pack(fill=tk.X, pady=(5, 10))
# زر الاعتماد للتدريب
train_frame = Frame(review_frame, bg=self.COLORS["bg_dark"])
train_frame.pack(fill=tk.X, pady=10)
self._approve_btn = Button(
train_frame, text="اعتماد للتدريب (Fine-tuning)",
command=self._approve_for_training,
bg=self.COLORS["accent_orange"], fg="white",
font=("Arial", 11, "bold"), relief=tk.FLAT,
padx=20, pady=8, cursor="hand2",
)
self._approve_btn.pack(side=tk.LEFT, padx=5)
self._training_count_label = Label(
train_frame, text="بيانات التدريب: 0",
bg=self.COLORS["bg_dark"], fg=self.COLORS["fg_secondary"],
font=("Arial", 10),
)
self._training_count_label.pack(side=tk.LEFT, padx=15)
# ==================================================================
# تبويب البحث الشامل
# ==================================================================
def _create_search_tab(self):
"""إنشاء تبويب البحث الشامل."""
tab = Frame(self.notebook, bg=self.COLORS["bg_dark"])
self.notebook.add(tab, text=" البحث الشامل ")
# شريط البحث
search_frame = Frame(tab, bg=self.COLORS["bg_dark"])
search_frame.pack(fill=tk.X, padx=10, pady=10)
Label(search_frame, text="البحث:",
bg=self.COLORS["bg_dark"], fg=self.COLORS["fg_primary"],
font=("Arial", 10)).pack(side=tk.LEFT, padx=(0, 5))
search_entry = Entry(
search_frame, textvariable=self.search_query,
bg=self.COLORS["bg_input"], fg=self.COLORS["fg_primary"],
insertbackground="white", font=("Arial", 11),
width=40,
)
search_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)
search_entry.bind("<Return>", lambda e: self._perform_search())
Button(
search_frame, text="بحث", command=self._perform_search,
bg=self.COLORS["accent_blue"], fg="white",
font=("Arial", 10, "bold"), relief=tk.FLAT,
padx=20, pady=5, cursor="hand2",
).pack(side=tk.LEFT, padx=5)
# خيارات البحث
options_frame = Frame(tab, bg=self.COLORS["bg_dark"])
options_frame.pack(fill=tk.X, padx=10, pady=5)
Label(options_frame, text="التصنيف:",
bg=self.COLORS["bg_dark"], fg=self.COLORS["fg_secondary"],
font=("Arial", 9)).pack(side=tk.LEFT, padx=(0, 5))
self._category_filter = ttk.Combobox(
options_frame, values=["الكل"], width=15,
state="readonly",
)
self._category_filter.set("الكل")
self._category_filter.pack(side=tk.LEFT, padx=5)
Label(options_frame, text="الحد الأدنى للثقة:",
bg=self.COLORS["bg_dark"], fg=self.COLORS["fg_secondary"],
font=("Arial", 9)).pack(side=tk.LEFT, padx=(15, 5))
conf_scale = tk.Scale(
options_frame, from_=0, to=1, resolution=0.05,
orient=tk.HORIZONTAL, variable=self.confidence_threshold,
bg=self.COLORS["bg_dark"], fg=self.COLORS["fg_primary"],
troughcolor=self.COLORS["bg_light"],
highlightthickness=0, length=100,
)
conf_scale.pack(side=tk.LEFT, padx=5)
# نتائج البحث
self.search_results = scrolledtext.ScrolledText(
tab, bg=self.COLORS["bg_input"], fg=self.COLORS["fg_primary"],
font=("Arial", 10), wrap=tk.WORD, height=20,
insertbackground="white",
)
self.search_results.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self.search_results.tag_configure("filename", foreground=self.COLORS["accent_blue"],
font=("Arial", 10, "bold"))
self.search_results.tag_configure("snippet", foreground=self.COLORS["fg_primary"])
self.search_results.tag_configure("meta", foreground=self.COLORS["fg_secondary"])
# ==================================================================
# تبويب الإحصائيات
# ==================================================================
def _create_stats_tab(self):
"""إنشاء تبويب الإحصائيات."""
tab = Frame(self.notebook, bg=self.COLORS["bg_dark"])
self.notebook.add(tab, text=" الإحصائيات ")
btn_frame = Frame(tab, bg=self.COLORS["bg_dark"])
btn_frame.pack(fill=tk.X, padx=10, pady=10)
Button(
btn_frame, text="تحديث الإحصائيات",
command=self._refresh_stats,
bg=self.COLORS["accent_blue"], fg="white",
font=("Arial", 10, "bold"), relief=tk.FLAT,
padx=15, pady=5, cursor="hand2",
).pack(side=tk.LEFT, padx=5)
Button(
btn_frame, text="تصدير التقرير",
command=self._export_stats,
bg=self.COLORS["accent_green"], fg="white",
font=("Arial", 10, "bold"), relief=tk.FLAT,
padx=15, pady=5, cursor="hand2",
).pack(side=tk.LEFT, padx=5)
self.stats_area = scrolledtext.ScrolledText(
tab, bg=self.COLORS["bg_input"], fg=self.COLORS["fg_primary"],
font=("Courier New", 10), wrap=tk.WORD,
insertbackground="white", height=25,
)
self.stats_area.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)
self.stats_area.tag_configure("header", foreground=self.COLORS["accent_blue"],
font=("Courier New", 12, "bold"))
self.stats_area.tag_configure("key", foreground=self.COLORS["accent_green"])
self.stats_area.tag_configure("value", foreground=self.COLORS["fg_primary"])
self.stats_area.tag_configure("separator", foreground=self.COLORS["border"])
# ==================================================================
# منطق المعالجة
# ==================================================================
def _select_input_folder(self):
"""اختيار مجلد الإدخال."""
folder = filedialog.askdirectory(title="اختر مجلد الملفات")
if folder:
self.selected_folder.set(folder)
self._log(f"تم اختيار المجلد: {folder}", "info")
def _select_output_folder(self):
"""اختيار مجلد المخرجات."""
folder = filedialog.askdirectory(title="اختر مجلد المخرجات")
if folder:
self.output_folder.set(folder)
def _start_processing(self):
"""بدء المعالجة."""
folder = self.selected_folder.get()
if not folder or not os.path.isdir(folder):
messagebox.showwarning("تنبيه", "يرجى اختيار مجلد صحيح")
return
self._start_btn.config(state=tk.DISABLED)
self._stop_btn.config(state=tk.NORMAL)
self.progress_var.set(0)
self._log("=" * 60, "header")
self._log("بدأت عملية المعالجة الذكية", "header")
self._log("=" * 60, "header")
# تشغيل في خيط منفصل
self._processing = True
thread = threading.Thread(target=self._processing_worker, daemon=True)
thread.start()
def _processing_worker(self):
"""عامل المعالجة (يعمل في خيط منفصل)."""
folder = self.selected_folder.get()
extensions = ['.pdf', '.png', '.jpg', '.jpeg', '.tiff', '.bmp']
# جمع الملفات
files = []
for root, _, filenames in os.walk(folder):
for fname in filenames:
ext = os.path.splitext(fname)[1].lower()
if ext in extensions:
files.append(os.path.join(root, fname))
if not files:
self._msg_queue.put(("warning", "لا توجد ملفات مدعومة في المجلد"))
self._msg_queue.put(("reset_buttons", None))
return
self._msg_queue.put(("info", f"تم العثور على {len(files)} ملف"))
# تهيئة المكونات
try:
from modules.core.file_fingerprint import FileFingerprintManager
from modules.core.classifier import MedicalClassifier
from modules.core.dataset_generator import DatasetGenerator
fp_mgr = FileFingerprintManager()
classifier = MedicalClassifier()
dataset_gen = DatasetGenerator(output_dir="training_data")
new_count = 0
cached_count = 0
for i, filepath in enumerate(files):
if not self._processing:
self._msg_queue.put(("warning", "تم إيقاف المعالجة"))
break
fname = os.path.basename(filepath)
progress = (i + 1) / len(files) * 100
self._msg_queue.put(("progress", progress))
# فحص البصمة
if fp_mgr.is_new_file(filepath):
new_count += 1
self._msg_queue.put(("info", f"[{i+1}/{len(files)}] جاري معالجة: {fname}"))
# محاولة استخراج النص
text = self._extract_text(filepath)
if text:
# تصنيف
result = classifier.classify(text)
category = result["category"]
confidence = result["confidence"]
# تسجيل البصمة
fp_mgr.mark_processed(
filepath, category=category,
confidence_score=confidence
)
# حفظ النص
output = self.output_folder.get() or folder
os.makedirs(os.path.join(output, category), exist_ok=True)
txt_path = os.path.join(output, category, f"{fname}.txt")
with open(txt_path, "w", encoding="utf-8") as f:
f.write(text)
self._msg_queue.put((
"success",
f"تم: {fname}{category} ({confidence:.0%})"
))
# إضافة لبيانات التدريب
dataset_gen.add_entry(
input_text=text,
output_text=text,
specialty=category,
quality="auto" if confidence > 0.5 else "draft",
source_file=fname,
)
else:
self._msg_queue.put(("error", f"فشل استخراج النص: {fname}"))
else:
cached_count += 1
self._msg_queue.put(("info", f"تخطى: {fname} (موجود سابقاً)"))
# ملخص
self._msg_queue.put(("header", "=" * 60))
self._msg_queue.put(("header", f"اكتملت المعالجة!"))
self._msg_queue.put(("success", f"جديد: {new_count} | متخطى: {cached_count}"))
self._msg_queue.put(("info", f"إجمالي: {len(files)} ملف"))
# تصدير بيانات التدريب
if new_count > 0:
dataset_gen.export("jsonl")
self._msg_queue.put((
"success",
f"تم تصدير {len(dataset_gen)} إدخال لبيانات التدريب"
))
except Exception as e:
self._msg_queue.put(("error", f"خطأ في المعالجة: {str(e)}"))
self._msg_queue.put(("progress", 100))
self._msg_queue.put(("reset_buttons", None))
def _stop_processing(self):
"""إيقاف المعالجة."""
self._processing = False
self._log("جاري إيقاف المعالجة...", "warning")
def _extract_text(self, filepath: str) -> Optional[str]:
"""استخراج النص من الملف."""
ext = os.path.splitext(filepath)[1].lower()
try:
if ext == '.pdf':
return self._extract_pdf(filepath)
elif ext in ('.png', '.jpg', '.jpeg', '.tiff', '.bmp'):
return self._extract_image(filepath)
except Exception as e:
self._msg_queue.put(("error", f"خطأ في {os.path.basename(filepath)}: {e}"))
return None
def _extract_pdf(self, filepath: str) -> Optional[str]:
"""استخراج النص من PDF."""
try:
import fitz # PyMuPDF
doc = fitz.open(filepath)
text = ""
for page in doc:
text += page.get_text()
doc.close()
return text.strip() if text.strip() else None
except ImportError:
self._msg_queue.put(("warning", "PyMuPDF غير مثبت — لا يمكن قراءة PDF"))
return None
def _extract_image(self, filepath: str) -> Optional[str]:
"""استخراج النص من الصورة باستخدام OCR."""
try:
import easyocr
reader = easyocr.Reader(['ar', 'en'], gpu=False)
results = reader.readtext(filepath)
text = " ".join([r[1] for r in results])
return text.strip() if text.strip() else None
except ImportError:
self._msg_queue.put(("warning", "EasyOCR غير مثبت — لا يمكن قراءة الصور"))
return None
except Exception as e:
self._msg_queue.put(("error", f"خطأ في OCR: {e}"))
return None
# ==================================================================
# التدقيق والاعتماد
# ==================================================================
def _run_spell_check(self):
"""تشغيل التدقيق اللغوي."""
text = self.original_text.get("1.0", tk.END).strip()
if not text:
messagebox.showinfo("تنبيه", "أدخل نصاً للتدقيق")
return
try:
from modules.nlp.language_corrector import LanguageCorrector
corrector = LanguageCorrector(lang='ar')
result = corrector.check(text)
self.corrected_text.delete("1.0", tk.END)
self.corrected_text.insert(tk.END, result["corrected"])
self._log(f"التدقيق: {result['error_count']} خطأ ({result['method']})", "info")
# إظهار الملخص
summary = corrector.get_error_summary(result)
if result["error_count"] > 0:
self._log(summary, "warning")
except Exception as e:
messagebox.showerror("خطأ", f"فشل التدقيق: {e}")
def _run_classification(self):
"""تشغيل تصنيف المحتوى."""
text = self.original_text.get("1.0", tk.END).strip()
if not text:
messagebox.showinfo("تنبيه", "أدخل نصاً للتصنيف")
return
try:
from modules.core.classifier import MedicalClassifier
clf = MedicalClassifier()
result = clf.classify(text)
category = result["category"]
confidence = result["confidence"]
msg = f"التصنيف: {category} (الثقة: {confidence:.0%})"
self._log(msg, "success")
if result.get("top_keywords"):
self._log(f"الكلمات المفتاحية: {', '.join(result['top_keywords'][:5])}", "info")
except Exception as e:
messagebox.showerror("خطأ", f"فشل التصنيف: {e}")
def _approve_for_training(self):
"""اعتماد النص الحالي لبيانات التدريب."""
original = self.original_text.get("1.0", tk.END).strip()
corrected = self.corrected_text.get("1.0", tk.END).strip()
if not corrected:
messagebox.showinfo("تنبيه", "لا يوجد نص للاعتماد")
return
try:
from modules.core.dataset_generator import DatasetGenerator
gen = DatasetGenerator(output_dir="training_data")
gen.add_entry(
input_text=original or corrected,
output_text=corrected,
quality="verified",
specialty="orthopedic",
)
gen.export("jsonl")
self._log("تم اعتماد النص لبيانات التدريب", "success")
self._training_count_label.config(text=f"بيانات التدريب: {len(gen)}")
messagebox.showinfo("نجاح", "تمت إضافة النص إلى بيانات التدريب بنجاح!")
except Exception as e:
messagebox.showerror("خطأ", f"فشل الحفظ: {e}")
# ==================================================================
# البحث
# ==================================================================
def _perform_search(self):
"""تنفيذ البحث."""
query = self.search_query.get().strip()
if not query:
messagebox.showinfo("تنبيه", "أدخل كلمة البحث")
return
self.search_results.delete("1.0", tk.END)
self._log(f"جاري البحث عن: {query}", "info")
try:
from modules.core.search_engine import SearchEngine
engine = SearchEngine()
results = engine.search(query, limit=50)
total = results.get("total_count", 0)
if total == 0:
self.search_results.insert(tk.END, f"لم يتم العثور على نتائج لـ: {query}\n\n")
return
self.search_results.insert(
tk.END,
f"تم العثور على {total} نتيجة:\n", "header"
)
for r in results.get("results", []):
fname = r.get("file_name", "Unknown")
cat = r.get("category", "N/A")
conf = r.get("confidence_score", 0)
snippet = r.get("snippet", "")
self.search_results.insert(tk.END, f"\n--- {fname}", "filename")
self.search_results.insert(tk.END, f" [{cat}] ({conf:.0%})\n", "meta")
if snippet:
self.search_results.insert(tk.END, f" {snippet}\n", "snippet")
self._log(f"البحث: {total} نتيجة", "success")
engine.close()
except Exception as e:
self.search_results.insert(tk.END, f"خطأ في البحث: {e}\n", "error")
# ==================================================================
# الإحصائيات
# ==================================================================
def _refresh_stats(self):
"""تحديث الإحصائيات."""
self.stats_area.delete("1.0", tk.END)
try:
from modules.core.file_fingerprint import FileFingerprintManager
fp = FileFingerprintManager()
stats = fp.get_statistics()
self.stats_area.insert(tk.END, "إحصائيات الأرشيف الرقمي\n", "header")
self.stats_area.insert(tk.END, "=" * 50 + "\n\n", "separator")
self.stats_area.insert(tk.END, "الملفات:\n", "key")
self.stats_area.insert(tk.END, f" الإجمالي: {stats.get('total_files', 0)}\n", "value")
self.stats_area.insert(tk.END, f" متوسط الثقة: {stats.get('average_confidence', 0):.1%}\n", "value")
self.stats_area.insert(tk.END, f" الحجم الكلي: {stats.get('total_size_mb', 0):.1f} MB\n", "value")
self.stats_area.insert(tk.END, f" متوسط المعالجة: {stats.get('average_processing_time', 0):.1f} ثانية\n\n", "value")
cats = stats.get("by_category", [])
if cats:
self.stats_area.insert(tk.END, "التصنيفات:\n", "key")
for cat in cats:
self.stats_area.insert(
tk.END,
f" {cat.get('category', 'N/A'):20s} {cat.get('count', 0)} ملف\n",
"value"
)
exts = stats.get("by_extension", [])
if exts:
self.stats_area.insert(tk.END, "\nالامتدادات:\n", "key")
for ext in exts[:10]:
self.stats_area.insert(
tk.END,
f" {ext.get('file_extension', 'N/A'):10s} {ext.get('count', 0)} ملف\n",
"value"
)
fp.close()
except Exception as e:
self.stats_area.insert(tk.END, f"خطأ في تحميل الإحصائيات: {e}\n", "error")
def _export_stats(self):
"""تصدير الإحصائيات."""
filepath = filedialog.asksaveasfilename(
defaultextension=".json",
filetypes=[("JSON", "*.json"), ("All files", "*.*")],
title="حفظ التقرير",
)
if not filepath:
return
try:
from modules.core.file_fingerprint import FileFingerprintManager
fp = FileFingerprintManager()
fp.export_fingerprints(filepath)
fp.close()
self._log(f"تم تصدير التقرير إلى: {filepath}", "success")
messagebox.showinfo("نجاح", "تم تصدير التقرير بنجاح!")
except Exception as e:
messagebox.showerror("خطأ", f"فشل التصدير: {e}")
# ==================================================================
# أدوات إضافية
# ==================================================================
def _find_duplicates(self):
"""كشف الملفات المكررة."""
folder = self.selected_folder.get()
if not folder:
folder = filedialog.askdirectory(title="اختر المجلد للفحص")
if not folder:
return
self._log("جاري البحث عن الملفات المكررة...", "info")
try:
from modules.core.file_fingerprint import FileFingerprintManager
fp = FileFingerprintManager()
duplicates = fp.find_duplicates(folder)
fp.close()
if not duplicates:
self._log("لم يتم العثور على ملفات مكررة", "success")
else:
total_dupes = sum(len(g) - 1 for g in duplicates)
self._log(f"تم العثور على {total_dupes} ملف مكرر في {len(duplicates)} مجموعة", "warning")
for group in duplicates[:10]:
names = [os.path.basename(f["path"]) for f in group]
self._log(f" متكرر: {', '.join(names)}", "info")
except Exception as e:
messagebox.showerror("خطأ", f"فشل الفحص: {e}")
def _cleanup_records(self):
"""تنظيف السجلات القديمة."""
try:
from modules.core.file_fingerprint import FileFingerprintManager
fp = FileFingerprintManager()
deleted = fp.cleanup_old_records(days=90)
fp.close()
self._log(f"تم حذف {deleted} سجل قديم", "success")
messagebox.showinfo("نجاح", f"تم حذف {deleted} سجل")
except Exception as e:
messagebox.showerror("خطأ", f"فشل التنظيف: {e}")
def _toggle_watchdog(self):
"""تشغيل/إيقاف المراقبة التلقائية."""
folder = self.selected_folder.get()
if not folder:
self.auto_watch_var.set(False)
messagebox.showwarning("تنبيه", "اختر مجلد أولاً")
return
if self.auto_watch_var.get():
try:
from modules.core.watchdog_service import FolderWatchdog
self._watchdog = FolderWatchdog(
watch_dir=folder,
callback=self._watchdog_callback,
poll_interval=3.0,
)
self._watchdog.start()
self._log(f"بدأت مراقبة: {folder}", "success")
except Exception as e:
self.auto_watch_var.set(False)
messagebox.showerror("خطأ", f"فشل بدء المراقبة: {e}")
else:
if self._watchdog:
self._watchdog.stop()
self._log("تم إيقاف المراقبة", "info")
def _watchdog_callback(self, filepath: str):
"""دالة الاستدعاء لمراقب المجلدات."""
self._msg_queue.put(("info", f"ملف جديد: {os.path.basename(filepath)}"))
def _show_about(self):
"""عرض معلومات البرنامج."""
messagebox.showinfo(
"عن OmniFile AI Processor",
"OmniFile AI Processor v4.2.0\n\n"
"نظام ذكاء اصطناعي متكامل لمعالجة الملفات\n"
"والنصوص العربية مع التركيز على المحتوى الطبي\n\n"
"المطور: Dr. Abdulmalek Tamer Al-husseini\n"
"الرخصة: MIT License\n\n"
"المحركات المدعومة:\n"
"• Tesseract OCR\n"
"• EasyOCR\n"
"• PaddleOCR\n"
"• TrOCR\n"
"• Surya OCR (معطل حالياً)"
)
def _check_initialization(self):
"""فحص توفر المحركات."""
status_parts = []
try:
import easyocr
status_parts.append("EasyOCR ✓")
except ImportError:
status_parts.append("EasyOCR ✗")
try:
import fitz
status_parts.append("PyMuPDF ✓")
except ImportError:
status_parts.append("PyMuPDF ✗")
try:
from modules.core.classifier import MedicalClassifier
status_parts.append("Classifier ✓")
except Exception:
status_parts.append("Classifier ✗")
self._engine_status.config(text=" | ".join(status_parts))
# ==================================================================
# أدوات مساعدة
# ==================================================================
def _log(self, message: str, tag: str = "info"):
"""إضافة رسالة لمنطقة السجلات."""
self.log_area.insert(tk.END, message + "\n", tag)
self.log_area.see(tk.END)
def _process_messages(self):
"""معالجة الرسائل من الخيوط."""
try:
while True:
msg_type, content = self._msg_queue.get_nowait()
if msg_type == "reset_buttons":
self._start_btn.config(state=tk.NORMAL)
self._stop_btn.config(state=tk.DISABLED)
elif msg_type == "progress":
self.progress_var.set(content)
else:
self._log(str(content), msg_type)
except queue.Empty:
pass
self.root.after(100, self._process_messages)
def main():
"""نقطة الدخول الرئيسية."""
root = tk.Tk()
# محاولة تعيين أيقونة التطبيق
try:
icon_path = os.path.join(PROJECT_ROOT, "docs", "author.png")
if os.path.exists(icon_path):
root.iconphoto(True, tk.PhotoImage(file=icon_path))
except Exception:
pass
app = OmniFileGUI(root)
root.protocol("WM_DELETE_WINDOW", lambda: (root.quit(), root.destroy()))
root.mainloop()
if __name__ == "__main__":
main()