# app.py # 🔍 نسخه پیشرفته - رابط Gradio تاریک و ساده - رفع مشکل نمایش نتایج # ============================================================================ # 🔧 Import کتابخانه‌ها # ============================================================================ import pytesseract from pdf2image import convert_from_path from PIL import Image, ImageEnhance, ImageFilter import re import json from typing import Dict, Any, List, Tuple, Optional import os from datetime import datetime import numpy as np from transformers import pipeline, AutoTokenizer, AutoModel, AutoModelForTokenClassification from sentence_transformers import SentenceTransformer, util import torch import arabic_reshaper from bidi.algorithm import get_display import gradio as gr # تنظیم مسیر Tesseract (در Hugging Face این مسیر ممکن است لازم نباشد) # pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract' # فقط در Colab # ============================================================================ # 🧠 بخش ۱: مدل‌های هوش مصنوعی با دقت بالا # ============================================================================ class HighAccuracyAIModels: """مدل‌های هوش مصنوعی با دقت فوق العاده""" def __init__(self): self.setup_high_accuracy_models() def setup_high_accuracy_models(self): """بارگذاری مدل‌های با دقت بالا""" print("🧠 در حال بارگذاری مدل‌ها...") try: self.ner_pipeline = pipeline( "token-classification", model="HooshvareLab/bert-fa-zwnj-base-ner", aggregation_strategy="max", device=0 if torch.cuda.is_available() else -1 ) print("✅ مدل NER بارگذاری شد") except Exception as e: print(f"⚠️ خطا در بارگذاری مدل NER: {e}") self.ner_pipeline = None try: self.embedding_model = SentenceTransformer( 'all-MiniLM-L6-v2', device='cuda' if torch.cuda.is_available() else 'cpu' ) print("✅ مدل Embedding بارگذاری شد") except Exception as e: print(f"⚠️ خطا در بارگذاری مدل Embedding: {e}") self.embedding_model = None # ============================================================================ # 🔍 بخش ۲: OCR با دقت فوق العاده # ============================================================================ class UltraAccuracyOCREngine: """موتور OCR با دقت فوق العاده""" def __init__(self, ai_models): self.ai_models = ai_models self.setup_advanced_ocr() def setup_advanced_ocr(self): """تنظیمات پیشرفته OCR""" self.tesseract_configs = [ r'--oem 3 --psm 6 -c tessedit_char_whitelist=آابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهیءةيك۰۱۲۳۴۵۶۷۸۹٠١٢٣٤٥٦٧٨٩ :.,-()', r'--oem 3 --psm 4 -c preserve_interword_spaces=1' ] def extract_text_ultra_accurate(self, input_file, progress_callback=None) -> Tuple[str, Dict[str, Any]]: """استخراج متن با دقت فوق العاده""" if progress_callback: progress_callback(5, "شروع استخراج متن...") results = { 'methods': {}, 'quality_metrics': {} } try: if progress_callback: progress_callback(20, "پردازش صفحات...") tesseract_results = self._advanced_tesseract_extraction(input_file, progress_callback) results['methods']['tesseract_advanced'] = tesseract_results combined_text = tesseract_results['text'] results['combined_text'] = combined_text results['quality_metrics'] = self._calculate_comprehensive_quality(combined_text) if progress_callback: progress_callback(80, "استخراج متن کامل شد") return combined_text, results except Exception as e: print(f"❌ خطا در استخراج متن: {e}") return "", results def _advanced_tesseract_extraction(self, input_file, progress_callback=None) -> Dict[str, Any]: """استخراج پیشرفته با Tesseract""" start_time = datetime.now() try: if isinstance(input_file, str) and input_file.lower().endswith('.pdf'): images = convert_from_path(input_file, first_page=1, last_page=3, dpi=300) all_texts = [] for i, image in enumerate(images): if progress_callback: progress_callback(20 + (i * 15), f"پردازش صفحه {i+1}...") processed_image = self._preprocess_image(image) page_texts = [] for config in self.tesseract_configs: try: text = pytesseract.image_to_string( processed_image, lang='fas+eng', config=config ) if text.strip(): page_texts.append(text) except: continue if page_texts: best_page_text = max(page_texts, key=lambda x: len(x)) all_texts.append(best_page_text) final_text = '\n'.join(all_texts) else: if isinstance(input_file, str): image = Image.open(input_file) else: image = input_file processed_image = self._preprocess_image(image) final_text = pytesseract.image_to_string(processed_image, lang='fas+eng') processing_time = (datetime.now() - start_time).total_seconds() return { 'text': final_text, 'confidence': 0.8, 'processing_time': processing_time, 'method': 'tesseract_advanced' } except Exception as e: print(f"⚠️ خطا در Tesseract: {e}") return {'text': '', 'confidence': 0, 'processing_time': 0, 'method': 'error'} def _preprocess_image(self, image): """پیش‌پردازش تصویر""" try: if image.mode != 'L': image = image.convert('L') enhancer = ImageEnhance.Contrast(image) image = enhancer.enhance(1.3) return image except: return image def _calculate_comprehensive_quality(self, text: str) -> Dict[str, float]: """محاسبه کیفیت جامع متن""" lines = [line.strip() for line in text.split('\n') if line.strip()] persian_chars = len(re.findall(r'[آ-ی]', text)) total_chars = len(text) return { 'overall_score': min(persian_chars / max(total_chars, 1) * 2, 1.0), 'line_count': len(lines), 'persian_ratio': persian_chars / total_chars if total_chars > 0 else 0, 'total_chars': total_chars } # ============================================================================ # 🤖 بخش ۳: سیستم RAG ساده‌سازی شده # ============================================================================ class SimpleRAGSystem: """سیستم RAG ساده‌سازی شده""" def __init__(self): # حذف کلید API یا استفاده از متغیر محیطی self.api_key = os.getenv("OPENROUTER_API_KEY", "") if not self.api_key: print("⚠️ کلید API یافت نشد. مدل RAG غیرفعال است.") self.setup_llm() self.knowledge_base = None def setup_llm(self): """تنظیم مدل زبانی""" if not self.api_key: print("❌ کلید API تنظیم نشده است.") self.llm = None self.embed_model = None return try: from llama_index.llms.openrouter import OpenRouter from llama_index.embeddings.huggingface import HuggingFaceEmbedding self.llm = OpenRouter( model="meta-llama/llama-3-70b-instruct", temperature=0.1, max_tokens=2000, api_key=self.api_key ) self.embed_model = HuggingFaceEmbedding( model_name="sentence-transformers/all-MiniLM-L6-v2" ) print("✅ مدل زبانی RAG تنظیم شد") except Exception as e: print(f"❌ خطا در تنظیم مدل RAG: {e}") self.llm = None self.embed_model = None def create_knowledge_base(self, text: str): """ایجاد پایگاه دانش""" if not self.llm: return False try: from llama_index.core import Document, VectorStoreIndex short_text = text[:2000] document = Document(text=short_text) self.knowledge_base = VectorStoreIndex.from_documents( [document], embed_model=self.embed_model ) return True except Exception as e: print(f"❌ خطا در ایجاد پایگاه دانش: {e}") return False # ============================================================================ # 🚀 بخش ۴: استخراج‌کننده متادیتا # ============================================================================ class APIEnhancedMetadataExtractor: """استخراج کننده متادیتا""" def __init__(self, rag_system): self.rag_system = rag_system def extract_with_api_power(self, text: str, progress_callback=None) -> Dict[str, Any]: """استخراج متادیتا با قدرت API""" if progress_callback: progress_callback(85, "استخراج اطلاعات با هوش مصنوعی...") if not self.rag_system.llm: return self._extract_without_api(text) try: chunk = text[:2000] prompt = f""" از متن زیر اطلاعات کتاب را استخراج کن: {chunk} اطلاعات مورد نیاز: - عنوان کتاب - نویسنده/مؤلف - مترجم (اگر وجود دارد) - ناشر - سال انتشار - شابک (ISBN) - نوبت چاپ پاسخ را به صورت JSON برگردان. """ metadata = self._call_api_for_extraction(prompt) return metadata except Exception as e: print(f"❌ خطا در استخراج با API: {e}") return self._extract_without_api(text) def _extract_without_api(self, text: str) -> Dict[str, Any]: """استخراج بدون API""" metadata = {} # استخراج عنوان lines = [line.strip() for line in text.split('\n') if line.strip()] for line in lines[:10]: if (10 <= len(line) <= 150 and len(re.findall(r'[آ-ی]', line)) >= 2 and not any(word in line for word in ['نویسنده', 'مؤلف', 'ناشر', 'چاپ', 'شابک'])): metadata['title'] = line break # استخراج سایر اطلاعات patterns = { 'author': r'نویسنده\s*[:\-]\s*([^\n]+)', 'publisher': r'ناشر\s*[:\-]\s*([^\n]+)', 'publication_year': r'سال\s*انتشار\s*[:\-]\s*([۱۳۴۰-۹]{4})', 'isbn': r'شابک\s*[:\-]\s*([۰-۹\-–]+)', 'edition': r'چاپ\s*(\d+)', 'translator': r'مترجم\s*[:\-]\s*([^\n]+)' } for field, pattern in patterns.items(): match = re.search(pattern, text, re.IGNORECASE) if match: metadata[field] = match.group(1).strip() return metadata def _call_api_for_extraction(self, prompt: str) -> Dict[str, Any]: """فراخوانی API""" try: from llama_index.core import Document doc = Document(text=prompt) query_engine = self.rag_system.knowledge_base.as_query_engine( llm=self.rag_system.llm, similarity_top_k=2 ) response = query_engine.query(prompt) response_text = str(response).strip() # استخراج JSON از پاسخ json_match = re.search(r'\{[^{}]*\}', response_text) if json_match: return json.loads(json_match.group()) return {} except Exception as e: print(f"⚠️ خطا در فراخوانی API: {e}") return {} # ============================================================================ # 📝 بخش ۵: استخراج‌کننده ساده # ============================================================================ class SimpleBookExtractor: """استخراج ساده اطلاعات کتاب""" def extract_basic_info(self, text: str) -> Dict[str, Any]: """استخراج اطلاعات پایه""" results = {} lines = text.split('\n') # استخراج عنوان for line in lines[:10]: line = line.strip() if (10 <= len(line) <= 150 and len(re.findall(r'[آ-ی]', line)) >= 2): results['title'] = line break for line in lines: line = line.strip() if not line: continue # استخراج سال year_match = re.search(r'۱۳[۷-۹][۰-۹]|۱۴۰[۰-۴]', line) if year_match and 'year' not in results: results['publication_year'] = year_match.group() # استخراج نویسنده author_match = re.search(r'نویسنده\s*[:\-]\s*(.+)', line, re.IGNORECASE) if author_match and 'author' not in results: results['author'] = author_match.group(1).strip() # استخراج ناشر publisher_match = re.search(r'ناشر\s*[:\-]\s*(.+)', line, re.IGNORECASE) if publisher_match and 'publisher' not in results: results['publisher'] = publisher_match.group(1).strip() # استخراج مترجم translator_match = re.search(r'مترجم\s*[:\-]\s*(.+)', line, re.IGNORECASE) if translator_match and 'translator' not in results: results['translator'] = translator_match.group(1).strip() # استخراج شابک isbn_match = re.search(r'شابک\s*[:\-]\s*([۰-۹\-–]+)', line, re.IGNORECASE) if isbn_match and 'isbn' not in results: results['isbn'] = isbn_match.group(1).strip() # استخراج نوبت چاپ edition_match = re.search(r'چاپ\s*(\d+)', line, re.IGNORECASE) if edition_match and 'edition' not in results: results['edition'] = edition_match.group(1).strip() return results def extract_additional_info(self, text: str) -> Dict[str, Any]: """استخراج اطلاعات تکمیلی""" results = {} patterns = { 'publisher': r'ناشر\s*[:\-]\s*(.+)', 'isbn': r'شابک\s*[:\-]\s*([۰-۹\-–]+)', 'translator': r'مترجم\s*[:\-]\s*(.+)', 'price': r'قیمت\s*[:\-]\s*([۰-۹,]+)', 'subject': r'موضوع\s*[:\-]\s*(.+)' } for field, pattern in patterns.items(): match = re.search(pattern, text, re.IGNORECASE) if match: results[field] = match.group(1).strip() return results # ============================================================================ # 🔄 بخش ۶: پردازشگر اصلی # ============================================================================ class UltraAccuracyBookProcessor: """پردازشگر اصلی""" def __init__(self): self.ai_models = HighAccuracyAIModels() self.ocr_engine = UltraAccuracyOCREngine(self.ai_models) self.rag_system = SimpleRAGSystem() self.api_extractor = APIEnhancedMetadataExtractor(self.rag_system) self.simple_extractor = SimpleBookExtractor() def process_book_ultra_accurate(self, input_file, progress_callback=None) -> Dict[str, Any]: """پردازش کتاب""" if progress_callback: progress_callback(0, "شروع پردازش...") start_time = datetime.now() # استخراج متن if progress_callback: progress_callback(10, "استخراج متن از فایل...") raw_text, ocr_results = self.ocr_engine.extract_text_ultra_accurate(input_file, progress_callback) if not raw_text or len(raw_text.strip()) < 10: return self._create_error_result("متن کافی استخراج نشد", ocr_results, start_time) # ایجاد پایگاه دانش if progress_callback: progress_callback(70, "راه‌اندازی هوش مصنوعی...") rag_ready = self.rag_system.create_knowledge_base(raw_text) if rag_ready: # استخراج با API api_metadata = self.api_extractor.extract_with_api_power(raw_text, progress_callback) backup_metadata = self._extract_backup_metadata(raw_text) final_metadata = self._final_fusion(api_metadata, backup_metadata, raw_text) else: # فقط روش معمولی if progress_callback: progress_callback(75, "استخراج اطلاعات...") final_metadata = self._extract_backup_metadata(raw_text) # ایجاد نتایج نهایی if progress_callback: progress_callback(95, "ذخیره‌سازی نتایج...") results = self._create_final_results(final_metadata, raw_text, ocr_results, rag_ready, start_time, input_file) if progress_callback: progress_callback(100, "پردازش کامل شد!") return results def _extract_backup_metadata(self, text: str) -> Dict[str, Any]: """استخراج پشتیبان""" basic = self.simple_extractor.extract_basic_info(text) additional = self.simple_extractor.extract_additional_info(text) # ترکیب اطلاعات پایه و تکمیلی combined_basic = basic.copy() for key, value in additional.items(): if key not in combined_basic and value: combined_basic[key] = value return { 'basic_info': combined_basic, 'additional_info': additional } def _final_fusion(self, api_metadata: Dict, backup_metadata: Dict, text: str) -> Dict[str, Any]: """ترکیب نهایی""" # ابتدا اطلاعات پشتیبان را کپی می‌کنیم final_basic = backup_metadata.get('basic_info', {}).copy() # سپس اطلاعات API را اضافه می‌کنیم (در صورت وجود) if api_metadata: for field, value in api_metadata.items(): if value and value not in ['یافت نشد', '']: final_basic[field] = value return { 'basic_info': final_basic, 'additional_info': backup_metadata.get('additional_info', {}), 'api_enhanced': bool(api_metadata and len(api_metadata) > 0) } def _create_final_results(self, metadata: Dict, text: str, ocr_results: Dict, rag_ready: bool, start_time: datetime, input_file) -> Dict[str, Any]: return { 'basic_info': metadata.get('basic_info', {}), 'additional_info': metadata.get('additional_info', {}), 'processing_time': (datetime.now() - start_time).total_seconds(), 'ocr_analysis': ocr_results, 'rag_available': rag_ready, 'api_enhanced': metadata.get('api_enhanced', False), 'total_text_length': len(text), 'file_info': { 'file_name': os.path.basename(input_file) if isinstance(input_file, str) else 'uploaded_file', 'file_type': 'PDF' if isinstance(input_file, str) and input_file.lower().endswith('.pdf') else 'Image' } } def _create_error_result(self, error: str, ocr_results: Dict, start_time: datetime): """ایجاد نتیجه خطا""" return { 'error': error, 'processing_time': (datetime.now() - start_time).total_seconds(), 'ocr_analysis': ocr_results } # ============================================================================ # 🎨 بخش ۷: رابط Gradio تاریک و ساده - نمایش کامل نتایج # ============================================================================ class GradioInterface: """رابط کاربری Gradio""" def __init__(self): self.processor = None self.current_progress = 0 self.current_status = "آماده" def initialize_processor(self): """راه‌اندازی پردازشگر""" if self.processor is None: self.processor = UltraAccuracyBookProcessor() def update_progress(self, progress, status): """به‌روزرسانی پیشرفت""" self.current_progress = progress self.current_status = status def process_file(self, file): """پردازش فایل""" if file is None: return "لطفاً یک فایل آپلود کنید", "", "", 0, "آماده" try: self.initialize_processor() file_path = file.name results = self.processor.process_book_ultra_accurate( file_path, progress_callback=self.update_progress ) if 'error' in results: return f"خطا: {results['error']}", "", "", 0, "خطا" report = self._generate_complete_report(results) download_info = self._save_results(results, file_path) json_output = self._generate_json_output(results) return report, download_info, json_output, 100, "پردازش کامل شد" except Exception as e: return f"خطا در پردازش: {str(e)}", "", "", 0, "خطا" def _generate_complete_report(self, results: Dict[str, Any]) -> str: """تولید گزارش کامل""" basic_info = results.get('basic_info', {}) additional_info = results.get('additional_info', {}) report = "📚 نتایج کامل استخراج اطلاعات کتاب\n" report += "=" * 50 + "\n\n" # اطلاعات اصلی کتاب report += "📖 اطلاعات اصلی:\n" report += "-" * 20 + "\n" main_fields = [ ('title', 'عنوان کتاب'), ('author', 'نویسنده/مؤلف'), ('translator', 'مترجم'), ('publisher', 'ناشر'), ('publication_year', 'سال انتشار'), ('isbn', 'شابک (ISBN)'), ('edition', 'نوبت چاپ') ] for field, title in main_fields: value = basic_info.get(field, "یافت نشد") report += f"• {title}: {value}\n" # اطلاعات تکمیلی if additional_info: report += "\n📋 اطلاعات تکمیلی:\n" report += "-" * 20 + "\n" for key, value in additional_info.items(): if value and value != "یافت نشد": display_key = self._translate_key(key) report += f"• {display_key}: {value}\n" # اطلاعات فنی report += "\n🔧 اطلاعات فنی:\n" report += "-" * 15 + "\n" report += f"• زمان پردازش: {results.get('processing_time', 0):.1f} ثانیه\n" report += f"• طول متن استخراج شده: {results.get('total_text_length', 0)} کاراکتر\n" report += f"• کیفیت OCR: {results.get('ocr_analysis', {}).get('quality_metrics', {}).get('overall_score', 0):.1%}\n" report += f"• سیستم هوش مصنوعی: {'فعال' if results.get('api_enhanced') else 'غیرفعال'}\n" # اطلاعات فایل file_info = results.get('file_info', {}) report += f"• نام فایل: {file_info.get('file_name', 'نامشخص')}\n" report += f"• نوع فایل: {file_info.get('file_type', 'نامشخص')}\n" report += f"\n🕒 تاریخ پردازش: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n" return report def _translate_key(self, key: str) -> str: """ترجمه کلیدهای انگلیسی به فارسی""" translations = { 'publisher': 'ناشر', 'isbn': 'شابک', 'price': 'قیمت', 'subject': 'موضوع', 'translator': 'مترجم', 'author': 'نویسنده', 'publication_year': 'سال انتشار', 'edition': 'نوبت چاپ', 'title': 'عنوان' } return translations.get(key, key) def _save_results(self, results: Dict[str, Any], file_path: str) -> str: """ذخیره نتایج""" try: base_name = os.path.splitext(os.path.basename(file_path))[0] timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") # ذخیره JSON json_filename = f"{base_name}_نتایج_{timestamp}.json" with open(json_filename, 'w', encoding='utf-8') as f: json.dump(results, f, ensure_ascii=False, indent=2) # ذخیره گزارش متنی txt_filename = f"{base_name}_گزارش_{timestamp}.txt" report_content = self._generate_complete_report(results) with open(txt_filename, 'w', encoding='utf-8') as f: f.write(report_content) return f"✅ فایل‌ها با موفقیت ذخیره شدند:\n📄 {json_filename}\n📝 {txt_filename}" except Exception as e: return f"⚠️ خطا در ذخیره‌سازی: {str(e)}" def _generate_json_output(self, results: Dict[str, Any]) -> str: """تولید خروجی JSON""" return json.dumps(results, ensure_ascii=False, indent=2) # ============================================================================ # 🚀 راه‌اندازی رابط Gradio تاریک و ساده # ============================================================================ def create_dark_simple_interface(): """ایجاد رابط تاریک و ساده""" interface = GradioInterface() # CSS برای رابط تاریک dark_css = """ .gradio-container { background: #000000 !important; color: #ffffff !important; font-family: Arial, sans-serif !important; } .container { background: #000000 !important; } .panel { background: #1a1a1a !important; border: 1px solid #333 !important; border-radius: 5px !important; padding: 10px !important; margin: 5px 0 !important; } .progress-text { color: #00ff00 !important; font-weight: bold; } .dark-button { background: #333 !important; color: white !important; border: 1px solid #555 !important; } .dark-button:hover { background: #444 !important; } .dark-input { background: #1a1a1a !important; color: white !important; border: 1px solid #333 !important; } .dark-slider { background: #333 !important; } .success-text { color: #00ff00 !important; } .error-text { color: #ff4444 !important; } """ with gr.Blocks( title="استخراج اطلاعات کتاب", css=dark_css ) as demo: gr.Markdown( """

📚 استخراج اطلاعات کتاب

آپلود فایل کتاب (PDF یا تصویر) برای استخراج خودکار اطلاعات

""" ) with gr.Row(): with gr.Column(scale=1): # بخش آپلود فایل gr.Markdown("### 📁 آپلود فایل") file_input = gr.File( label="", file_types=[".pdf", ".jpg", ".jpeg", ".png"], type="filepath", elem_classes="dark-input" ) # دکمه پردازش process_btn = gr.Button( "🚀 شروع پردازش", variant="primary", elem_classes="dark-button", size="lg" ) # بخش پیشرفت gr.Markdown("### 📊 پیشرفت") progress_bar = gr.Slider( minimum=0, maximum=100, value=0, label="", interactive=False, elem_classes="dark-slider" ) progress_text = gr.Textbox( label="وضعیت", value="آماده", interactive=False, elem_classes="dark-input" ) with gr.Column(scale=2): # بخش نتایج gr.Markdown("### 📄 نتایج کامل") output_report = gr.Textbox( label="", lines=12, show_copy_button=True, elem_classes="dark-input" ) # بخش ذخیره‌سازی gr.Markdown("### 💾 ذخیره‌سازی") download_info = gr.Textbox( label="", lines=3, interactive=False, elem_classes="dark-input" ) # بخش JSON gr.Markdown("### 🔧 خروجی فنی (JSON)") json_output = gr.Textbox( label="", lines=8, show_copy_button=True, elem_classes="dark-input" ) # اتصال رویداد process_btn.click( fn=interface.process_file, inputs=[file_input], outputs=[output_report, download_info, json_output, progress_bar, progress_text] ) gr.Markdown( """

سیستم استخراج خودکار اطلاعات کتاب - نسخه ساده و تاریک

📖 تمام اطلاعات کتاب به صورت کامل نمایش داده می‌شود

""" ) return demo # این بخش را تغییر می‌دهیم تا با Hugging Face سازگار شود def main(): demo = create_dark_simple_interface() demo.launch( server_name="0.0.0.0", # برای Hugging Face server_port=int(os.environ.get("PORT", 7860)), # پورت Hugging Face share=False # در Hugging Face نیازی به share نیست ) if __name__ == "__main__": main()