Spaces:
Sleeping
Sleeping
| """ | |
| الواجهة الرسومية المتكاملة لـ 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() | |