F-allahmoradi commited on
Commit
4e093a1
·
verified ·
1 Parent(s): 91848f2

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +919 -0
app.py ADDED
@@ -0,0 +1,919 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+
3
+ # ============================================================================
4
+ # 📦 ایمپورت کتابخانه‌ها
5
+ # ============================================================================
6
+
7
+ import os
8
+ import requests
9
+ import json
10
+ import re
11
+ import time
12
+ import threading
13
+ from PIL import Image, ImageEnhance
14
+ import pytesseract
15
+ from pdf2image import convert_from_path
16
+ import gradio as gr
17
+ from groq import Groq
18
+ import numpy as np
19
+ import cv2
20
+ from collections import Counter
21
+ import easyocr
22
+ from persian_tools import digits
23
+ from rapidfuzz import fuzz
24
+ from datetime import datetime
25
+ import concurrent.futures
26
+
27
+ # تنظیم مسیر Tesseract
28
+ pytesseract.pytesseract.tesseract_cmd = '/usr/bin/tesseract'
29
+
30
+ print("✅ کتابخانه‌ها بارگذاری شدند")
31
+
32
+ # ============================================================================
33
+ # 🔍 موتور OCR حرفه‌ای با DPI 200
34
+ # ============================================================================
35
+
36
+ class ProfessionalOCREngine:
37
+ """موتور OCR حرفه‌ای با کیفیت بالا"""
38
+ def __init__(self):
39
+ self.setup_professional_ocr()
40
+ self.setup_easyocr()
41
+
42
+ def setup_professional_ocr(self):
43
+ """تنظیمات حرفه‌ای OCR"""
44
+ self.tesseract_configs = [
45
+ '--oem 3 --psm 6 -c tessedit_char_whitelist=آابپتثجچحخدذرزژسشصضطظعغفقکگلمنوهیءةيك012345678۹۰۱۲۳۴۵۶۷۸۹ :.,-()',
46
+ '--oem 3 --psm 4 -c preserve_interword_spaces=1',
47
+ '--oem 3 --psm 8 -c tessedit_char_blacklist=|\\/><[]{}',
48
+ ]
49
+
50
+ def setup_easyocr(self):
51
+ """راه‌اندازی EasyOCR حرفه‌ای"""
52
+ try:
53
+ self.easy_reader = easyocr.Reader(['fa', 'en'], gpu=False)
54
+ self.easyocr_available = True
55
+ print("✅ EasyOCR حرفه‌ای راه‌اندازی شد")
56
+ except Exception as e:
57
+ print(f"⚠️ EasyOCR راه‌اندازی نشد: {e}")
58
+ self.easyocr_available = False
59
+
60
+ def extract_text_professional(self, input_file, num_pages=5):
61
+ """استخراج متن با روش حرفه‌ای - ۵ صفحه با DPI 200"""
62
+ try:
63
+ print(f"🔍 شروع استخراج متن حرفه‌ای از {num_pages} صفحه با DPI 200...")
64
+ if isinstance(input_file, str) and input_file.lower().endswith('.pdf'):
65
+ # استفاده از DPI 200 برای تعادل سرعت و کیفیت
66
+ images = convert_from_path(input_file, first_page=1, last_page=num_pages, dpi=200)
67
+ all_texts = []
68
+ for i, image in enumerate(images):
69
+ print(f"📄 پردازش صفحه {i+1} از {num_pages} با DPI 200...")
70
+ # پردازش حرفه‌ای با کیفیت بالا
71
+ tesseract_text = self._extract_with_pro_tesseract(image)
72
+ easyocr_text = self._extract_with_pro_easyocr(image) if self.easyocr_available else ""
73
+ # ترکیب پیشرفته
74
+ combined_text = self._advanced_combination([tesseract_text, easyocr_text])
75
+ if combined_text.strip():
76
+ page_result = f"""
77
+ {'='*40}
78
+ 📄 صفحه {i+1}:
79
+ {'='*40}
80
+ {combined_text}"""
81
+ all_texts.append(page_result)
82
+ print(f"✅ صفحه {i+1} پردازش شد: {len(combined_text)} کاراکتر")
83
+
84
+ result = '\n'.join(all_texts)
85
+ print(f"✅ پردازش {len(images)} صفحه با DPI 200 کامل شد: {len(result)} کاراکتر")
86
+ return result
87
+ else:
88
+ # فایل تصویری با کیفیت بالا
89
+ image = Image.open(input_file)
90
+ tesseract_text = self._extract_with_pro_tesseract(image)
91
+ easyocr_text = self._extract_with_pro_easyocr(image) if self.easyocr_available else ""
92
+ combined_text = self._advanced_combination([tesseract_text, easyocr_text])
93
+ result = f"""
94
+ {'='*40}
95
+ 📄 صفحه 1:
96
+ {'='*40}
97
+ {combined_text}"""
98
+ print(f"✅ پردازش تصویر با کیفیت بالا کامل شد: {len(combined_text)} کاراکتر")
99
+ return result
100
+ except Exception as e:
101
+ return f"❌ خطا در پردازش صفحات: {str(e)}"
102
+
103
+ def _extract_with_pro_tesseract(self, image):
104
+ """استخراج حرفه‌ای با Tesseract"""
105
+ try:
106
+ # پیش‌پردازش حرفه‌ای برای DPI 200
107
+ processed_images = [
108
+ self._preprocess_high_quality(image),
109
+ self._preprocess_enhanced_contrast(image),
110
+ self._preprocess_denoise_advanced(image)
111
+ ]
112
+ all_texts = []
113
+ for processed_img in processed_images:
114
+ for config in self.tesseract_configs:
115
+ try:
116
+ text = pytesseract.image_to_string(processed_img, lang='fas+eng', config=config)
117
+ if text.strip():
118
+ all_texts.append(text)
119
+ except:
120
+ continue
121
+ return self._select_best_quality_text(all_texts) if all_texts else ""
122
+ except Exception as e:
123
+ print(f"⚠️ خطا در Tesseract حرفه‌ای: {e}")
124
+ return ""
125
+
126
+ def _extract_with_pro_easyocr(self, image):
127
+ """استخراج حرفه‌ای با EasyOCR"""
128
+ try:
129
+ image_np = np.array(image)
130
+ # تنظیمات پیشرفته برای کیفیت بالا
131
+ results = self.easy_reader.readtext(image_np, paragraph=True, text_threshold=0.3, batch_size=1)
132
+ extracted_texts = []
133
+ for result in results:
134
+ if len(result) >= 2:
135
+ text = result[1]
136
+ confidence = result[2] if len(result) > 2 else 0.5
137
+ if confidence > 0.2: # آستانه پایین‌تر برای دریافت متن بیشتر
138
+ extracted_texts.append(text)
139
+ return " ".join(extracted_texts)
140
+ except Exception as e:
141
+ print(f"⚠️ خطا در EasyOCR حرفه‌ای: {e}")
142
+ return ""
143
+
144
+ def _preprocess_high_quality(self, image):
145
+ """پیش‌پردازش برای کیفیت بالا"""
146
+ try:
147
+ if image.mode != 'L':
148
+ image = image.convert('L')
149
+ # افزایش کنتراست برای DPI 200
150
+ enhancer = ImageEnhance.Contrast(image)
151
+ image = enhancer.enhance(2.5)
152
+ # افزایش وضوح
153
+ enhancer = ImageEnhance.Sharpness(image)
154
+ image = enhancer.enhance(2.0)
155
+ return image
156
+ except:
157
+ return image
158
+
159
+ def _preprocess_enhanced_contrast(self, image):
160
+ """پیش‌پردازش با کنتراست پیشرفته"""
161
+ try:
162
+ if image.mode != 'L':
163
+ image = image.convert('L')
164
+ # استفاده از CLAHE برای کنتراست پیشرفته
165
+ img_np = np.array(image)
166
+ clahe = cv2.createCLAHE(clipLimit=4.0, tileGridSize=(8,8))
167
+ img_contrast = clahe.apply(img_np)
168
+ return Image.fromarray(img_contrast)
169
+ except:
170
+ return image
171
+
172
+ def _preprocess_denoise_advanced(self, image):
173
+ """پیش‌پردازش حذف نویز پیشرفته"""
174
+ try:
175
+ if image.mode != 'L':
176
+ image = image.convert('L')
177
+ img_np = np.array(image)
178
+ # حذف نویز با فیلترهای پیشرفته
179
+ img_denoised = cv2.medianBlur(img_np, 3)
180
+ img_denoised = cv2.GaussianBlur(img_denoised, (1, 1), 0)
181
+ return Image.fromarray(img_denoised)
182
+ except:
183
+ return image
184
+
185
+ def _select_best_quality_text(self, text_list):
186
+ """انتخاب متن با بالاترین کیفیت"""
187
+ if not text_list:
188
+ return ""
189
+ scored_texts = []
190
+ for text in text_list:
191
+ score = self._calculate_advanced_quality(text)
192
+ scored_texts.append((text, score))
193
+ return max(scored_texts, key=lambda x: x[1])[0]
194
+
195
+ def _calculate_advanced_quality(self, text):
196
+ """محاسبه کیفیت پیشرفته متن"""
197
+ if not text.strip():
198
+ return 0
199
+ score = 0
200
+ # امتیاز بر اساس طول متن
201
+ if 50 <= len(text) <= 5000:
202
+ score += 3
203
+ # امتیاز بر اساس کاراکترهای فارسی
204
+ persian_chars = len(re.findall(r'[آ-ی]', text))
205
+ persian_ratio = persian_chars / len(text) if len(text) > 0 else 0
206
+ if persian_ratio > 0.3:
207
+ score += persian_ratio * 4
208
+ # امتیاز بر اساس ساختار متن
209
+ lines = [line.strip() for line in text.split('\n') if line.strip()]
210
+ if len(lines) > 2:
211
+ valid_lines = sum(1 for line in lines if 5 <= len(line) <= 200)
212
+ score += (valid_lines / len(lines)) * 3
213
+ # امتیاز بر اساس کلمات کلیدی کتاب
214
+ book_keywords = ['عنوان', 'نویسنده', 'مؤلف', 'ناشر', 'چاپ', 'شابک', 'قیمت', 'تیراژ', 'کتاب', 'انتشارات', 'مترجم', 'فهرست', 'مقدمه']
215
+ keyword_count = sum(1 for keyword in book_keywords if keyword in text)
216
+ score += keyword_count * 0.5
217
+ return score
218
+
219
+ def _advanced_combination(self, texts):
220
+ """ترکیب پیشرفته نتایج"""
221
+ valid_texts = [t for t in texts if t and t.strip()]
222
+ if not valid_texts:
223
+ return ""
224
+ if len(valid_texts) == 1:
225
+ return valid_texts[0]
226
+ # ترکیب هوشمند بر اساس کیفیت
227
+ best_text = max(valid_texts, key=lambda x: self._calculate_advanced_quality(x))
228
+ return best_text
229
+
230
+ # ============================================================================
231
+ # 🧠 استخراج‌کننده متادیتای حرفه‌ای
232
+ # ============================================================================
233
+
234
+ class ProfessionalMetadataExtractor:
235
+ """استخراج‌کننده متادیتای حرفه‌ای"""
236
+ def __init__(self):
237
+ self.setup_professional_patterns()
238
+
239
+ def setup_professional_patterns(self):
240
+ """الگوهای حرفه‌ای برای استخراج اطلاعات"""
241
+ self.patterns = {
242
+ 'title': [
243
+ r'عنوان\s*[:\-]\s*(.+?)(?=\n|$)',
244
+ r'نام\s*کتاب\s*[:\-]\s*(.+?)(?=\n|$)',
245
+ r'کتاب\s*[:\-]\s*(.+?)(?=\n|$)',
246
+ r'^(?!.*(نویسنده|مؤلف|ناشر|چاپ|شابک))(.{10,120}?)(?=\n|$)',
247
+ ],
248
+ 'author': [
249
+ r'نویسنده\s*[:\-]\s*(.+?)(?=\n|$)',
250
+ r'مؤلف\s*[:\-]\s*(.+?)(?=\n|$)',
251
+ r'پدیدآور\s*[:\-]\s*(.+?)(?=\n|$)',
252
+ r'تألیف\s*[:\-]\s*(.+?)(?=\n|$)',
253
+ ],
254
+ 'translator': [
255
+ r'مترجم\s*[:\-]\s*(.+?)(?=\n|$)',
256
+ r'ترجمه\s*[:\-]\s*(.+?)(?=\n|$)',
257
+ r'برگردان\s*[:\-]\s*(.+?)(?=\n|$)',
258
+ ],
259
+ 'publisher': [
260
+ r'ناشر\s*[:\-]\s*(.+?)(?=\n|$)',
261
+ r'انتشارات\s*[:\-]\s*(.+?)(?=\n|$)',
262
+ r'چاپ\s*[:\-]\s*(.+?)(?=\n|$)',
263
+ ],
264
+ 'publication_year': [
265
+ r'سال\s*انتشار\s*[:\-]\s*(\d{4})',
266
+ r'تاریخ\s*چاپ\s*[:\-]\s*(\d{4})',
267
+ r'چاپ\s*[:\-].*?(\d{4})',
268
+ r'(\d{4})\s*,\s*تیراژ',
269
+ r'۱۳[۴-۹]\d',
270
+ ],
271
+ 'edition': [
272
+ r'نوبت\s*چاپ\s*[:\-]\s*(\S+)',
273
+ r'چاپ\s*[:\-]\s*(\S+)',
274
+ r'چاپ\s*(اول|دوم|سوم|چهارم|پنجم|ششم|هفتم|هشتم|نهم|دهم)',
275
+ ],
276
+ 'isbn': [
277
+ r'شابک\s*[:\-]\s*([\d\-]+)',
278
+ r'ISBN\s*[:\-]\s*([\d\-]+)',
279
+ r'[\d\-]{10,17}',
280
+ ]
281
+ }
282
+
283
+ def extract_metadata_professional(self, extracted_text):
284
+ """استخراج متادیتا با روش حرفه‌ای"""
285
+ try:
286
+ if not extracted_text or len(extracted_text.strip()) < 100:
287
+ return self._get_empty_result("متن کافی برای تحلیل یافت نشد")
288
+
289
+ # استخراج پیشرفته
290
+ pattern_results = self._extract_with_advanced_patterns(extracted_text)
291
+ intelligent_results = self._intelligent_analysis(extracted_text)
292
+ combined_results = self._professional_combination(pattern_results, intelligent_results)
293
+ enhanced_results = self._enhance_with_context(combined_results, extracted_text)
294
+ validated_results = self._professional_validation(enhanced_results)
295
+ confidence = self._calculate_professional_confidence(validated_results)
296
+
297
+ return {
298
+ 'success': True,
299
+ 'metadata': validated_results,
300
+ 'confidence': confidence,
301
+ 'extraction_method': 'professional'
302
+ }
303
+ except Exception as e:
304
+ return self._get_empty_result(str(e))
305
+
306
+ def _extract_with_advanced_patterns(self, text):
307
+ """استخراج با الگوهای پیشرفته"""
308
+ results = {}
309
+ for field, patterns in self.patterns.items():
310
+ best_match = None
311
+ for pattern in patterns:
312
+ try:
313
+ matches = re.findall(pattern, text, re.IGNORECASE | re.MULTILINE)
314
+ for match in matches:
315
+ if isinstance(match, tuple):
316
+ match = match[0] if match[0] else (match[1] if len(match) > 1 else "")
317
+ if match:
318
+ clean_value = self._professional_clean(field, str(match))
319
+ if self._is_professionally_valid(field, clean_value):
320
+ if not best_match or len(clean_value) > len(best_match):
321
+ best_match = clean_value
322
+ except:
323
+ continue
324
+ if best_match:
325
+ results[field] = best_match
326
+ return results
327
+
328
+ def _intelligent_analysis(self, text):
329
+ """تحلیل هوشمند"""
330
+ lines = [line.strip() for line in text.split('\n') if line.strip()]
331
+ results = {}
332
+ # تحلیل عمیق خطوط
333
+ for i, line in enumerate(lines):
334
+ if i < 10: # فقط 10 خط اول برای کارایی
335
+ if not results.get('title') and self._is_professional_title(line, i):
336
+ results['title'] = self._professional_title_clean(line)
337
+ if not results.get('author') and self._contains_author_indicator(line):
338
+ author = self._extract_professional_author(line)
339
+ if author:
340
+ results['author'] = author
341
+ if not results.get('publisher') and self._contains_publisher_indicator(line):
342
+ publisher = self._extract_professional_publisher(line)
343
+ if publisher:
344
+ results['publisher'] = publisher
345
+ return results
346
+
347
+ def _is_professional_title(self, line, line_index):
348
+ """بررسی حرفه‌ای عنوان"""
349
+ if len(line) < 8 or len(line) > 150:
350
+ return False
351
+ # خطوط اول احتمال بیشتری برای عنوان دارند
352
+ title_probability = max(0.8 - (line_index * 0.1), 0.3)
353
+ exclude_patterns = [
354
+ r'نویسنده', r'مؤلف', r'ناشر', r'چاپ', r'شابک',
355
+ r'قیمت', r'تیراژ', r'صفحه', r'فهرست', r'مقدمه'
356
+ ]
357
+ if any(re.search(pattern, line) for pattern in exclude_patterns):
358
+ return False
359
+ persian_ratio = len(re.findall(r'[آ-ی]', line)) / len(line) if len(line) > 0 else 0
360
+ if persian_ratio < 0.4:
361
+ return False
362
+ return True
363
+
364
+ def _professional_combination(self, pattern_results, intelligent_results):
365
+ """ترکیب حرفه‌ای نتایج"""
366
+ combined = pattern_results.copy()
367
+ # اولویت با نتایج الگوها، سپس نتایج هوشمند
368
+ for field, value in intelligent_results.items():
369
+ if value and not combined.get(field):
370
+ combined[field] = value
371
+ return combined
372
+
373
+ def _professional_clean(self, field, value):
374
+ """پاکسازی حرفه‌ای"""
375
+ if not value:
376
+ return value
377
+ value = re.sub(r'[ـ\r\x200c\x200d]', '', value)
378
+ value = re.sub(r'\s+', ' ', value).strip()
379
+ # پاکسازی ویژه هر فیلد
380
+ cleaners = {
381
+ 'title': lambda x: re.sub(r'^[:\-\s]*', '', x),
382
+ 'author': lambda x: re.sub(r'^(نویسنده|مؤلف|پدیدآور)[:\-\s]*', '', x),
383
+ 'publisher': lambda x: re.sub(r'^(ناشر|انتشارات)[:\-\s]*', '', x),
384
+ 'publication_year': lambda x: re.sub(r'[^\d]', '', x),
385
+ }
386
+ if field in cleaners:
387
+ value = cleaners[field](value)
388
+ return value.strip()
389
+
390
+ def _is_professionally_valid(self, field, value):
391
+ """اعتبارسنجی حرفه‌ای"""
392
+ if not value:
393
+ return False
394
+ validators = {
395
+ 'title': lambda x: 5 <= len(x) <= 200,
396
+ 'author': lambda x: 3 <= len(x) <= 100,
397
+ 'publisher': lambda x: 3 <= len(x) <= 100,
398
+ 'publication_year': lambda x: x.isdigit() and 1300 <= int(x) <= 1500,
399
+ 'edition': lambda x: 1 <= len(x) <= 50,
400
+ 'isbn': lambda x: 10 <= len(x.replace('-', '')) <= 17
401
+ }
402
+ return field in validators and validators[field](value)
403
+
404
+ def _calculate_professional_confidence(self, results):
405
+ """محاسبه اطمینان حرفه‌ای"""
406
+ if not results:
407
+ return 0.0
408
+ weights = {
409
+ 'title': 0.25,
410
+ 'author': 0.20,
411
+ 'publisher': 0.15,
412
+ 'publication_year': 0.15,
413
+ 'edition': 0.10,
414
+ 'isbn': 0.10,
415
+ 'translator': 0.05
416
+ }
417
+ total_score = 0.0
418
+ for field, weight in weights.items():
419
+ if field in results and results[field]:
420
+ total_score += weight
421
+ return total_score
422
+
423
+ # ============================================================================
424
+ # 🤖 سیستم پردازش ترکیبی حرفه‌ای
425
+ # ============================================================================
426
+
427
+ class ProfessionalPersianBookProcessor:
428
+ def __init__(self):
429
+ # خواندن کلیدها از متغیرهای محیطی (Secrets)
430
+ key1 = os.getenv("GROQ_API_KEY_1")
431
+ key2 = os.getenv("GROQ_API_KEY_2")
432
+ key3 = os.getenv("GROQ_API_KEY_3")
433
+ self.groq_keys = [k for k in [key1, key2, key3] if k]
434
+ if not self.groq_keys:
435
+ print("⚠️ هیچ کلید Groq از Secrets یافت نشد. ممکن است عملکرد محدود شود.")
436
+ self.groq_keys = []
437
+ self.current_key_index = 0
438
+ self.ocr_engine = ProfessionalOCREngine()
439
+ self.metadata_extractor = ProfessionalMetadataExtractor()
440
+ print(f"✅ سیستم حرفه‌ای با {len(self.groq_keys)} کلید Groq راه‌اندازی شد")
441
+
442
+ def get_next_groq_client(self):
443
+ """دریافت کلاینت Groq بعدی"""
444
+ if not self.groq_keys:
445
+ return None
446
+ self.current_key_index = (self.current_key_index + 1) % len(self.groq_keys)
447
+ try:
448
+ return Groq(api_key=self.groq_keys[self.current_key_index])
449
+ except:
450
+ return None
451
+
452
+ def extract_text_professional(self, file_path, num_pages=5):
453
+ """استخراج متن حرفه‌ای - ۵ صفحه با DPI 200"""
454
+ return self.ocr_engine.extract_text_professional(file_path, num_pages)
455
+
456
+ def analyze_with_groq_professional(self, full_text):
457
+ """تحلیل حرفه‌ای با Groq - پرامپت پیشرفته"""
458
+ for key_index in range(len(self.groq_keys)):
459
+ client = self.get_next_groq_client()
460
+ if not client:
461
+ continue
462
+ try:
463
+ print(f" 🤖 تحلیل حرفه‌ای با Groq (کلید {key_index + 1})...")
464
+ # پرامپت حرفه‌ای و جامع
465
+ prompt = f"""
466
+ شما یک متخصص حرفه‌ای در تحلیل و استخراج اطلاعات از کتاب‌های فارسی هستید. لطفاً با دقت بالا اطلاعات زیر را از متن ۵ صفحه اول کتاب استخراج کنید.
467
+ **متن کامل ۵ صفحه اول کتاب:**
468
+ {full_text[:4000]}
469
+ **اطلاعات مورد نیاز برای استخراج:**
470
+ ۱. **عنوان اصلی کتاب** (title):
471
+ - دقیق‌ترین و کامل‌ترین عنوان را پیدا کنید
472
+ - عناوین فرعی را نیز در صورت وجود شامل شود
473
+ ۲. **نام نویسنده/مؤلف** (author):
474
+ - نام کامل نویسنده یا مؤلف
475
+ - در صورت وجود چند نویسنده، همه را ذکر کنید
476
+ ۳. **نام مترجم** (translator):
477
+ - اگر کتاب ترجمه است، نام کامل مترجم
478
+ - در صورت عدم ترجمه، "یافت نشد"
479
+ ۴. **نام ناشر** (publisher):
480
+ - نام کامل انتشارات یا ناشر
481
+ - شامل شهر در صورت ذکر شدن
482
+ ۵. **سال انتشار** (publication_year):
483
+ - سال چاپ به صورت عدد (مثال: 1402)
484
+ - از تاریخ‌های هجری شمسی استفاده شود
485
+ ۶. **شماره شابک** (isbn):
486
+ - شماره ۱۰ یا ۱۳ رقمی شابک
487
+ - با فرمت استاندارد
488
+ ۷. **نوبت چاپ** (edition):
489
+ - شماره یا عنوان نوبت چاپ
490
+ - مثال: اول، دوم، سوم...
491
+ ۸. **موضوع کتاب** (subject):
492
+ - حوزه موضوعی اصلی کتاب
493
+ - ژانر و زمینه محتوایی
494
+ ۹. **خلاصه محتوا** (summary):
495
+ - خلاصه‌ای جامع از محتوای ۵ صفحه اول
496
+ - حدود ۱۰۰-۱۵۰ کلمه
497
+ -突出重点 و مفاهیم اصلی
498
+ **دستورات مهم:**
499
+ - پاسخ را **فقط و فقط** به صورت JSON برگردانید
500
+ - از هیچ متن اضافی قبل یا بعد از JSON استفاده نکنید
501
+ - برای فیلدهایی که اطلاعاتی پیدا نکردید از "یافت نشد" استفاده کنید
502
+ - از قالب‌بندی استاندارد JSON استفاده کنید
503
+ - دقت و صحت اطلاعات اولویت دارد
504
+ **قالب خروجی JSON:**
505
+ {{
506
+ "title": "عنوان کامل کتاب",
507
+ "author": "نام کامل نویسنده",
508
+ "translator": "نام کامل مترجم",
509
+ "publisher": "نام کامل ناشر",
510
+ "publication_year": "سال انتشار",
511
+ "isbn": "شماره شابک",
512
+ "edition": "نوبت چاپ",
513
+ }}
514
+ **تأکید: فقط JSON خالص برگردانید، بدون هیچ توضیح اضافی!**
515
+ """
516
+ response = client.chat.completions.create(
517
+ messages=[{"role": "user", "content": prompt}],
518
+ model="llama-3.1-8b-instant",
519
+ temperature=0.1,
520
+ max_tokens=2000, # افزایش به 2000 توکن
521
+ timeout=30
522
+ )
523
+ result_text = response.choices[0].message.content
524
+ print(f" ✅ پاسخ Groq دریافت شد ({len(result_text)} کاراکتر)")
525
+ # استخراج پیشرفته JSON
526
+ json_match = re.search(r'\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}', result_text, re.DOTALL)
527
+ if json_match:
528
+ json_str = json_match.group()
529
+ try:
530
+ result_json = json.loads(json_str)
531
+ filled_fields = sum(1 for v in result_json.values() if v and v != "یافت نشد")
532
+ print(f" 📊 فیلدهای پر: {filled_fields} از ۹")
533
+ if filled_fields > 0:
534
+ return {
535
+ "success": True,
536
+ "source": "groq_professional",
537
+ "results": result_json,
538
+ "filled_fields": filled_fields,
539
+ "response_length": len(result_text)
540
+ }
541
+ except json.JSONDecodeError as e:
542
+ print(f" ❌ خطای JSON: {e}")
543
+ else:
544
+ print(" ⚠️ JSON در پاسخ یافت نشد")
545
+ except Exception as e:
546
+ print(f" ⚠️ خطا در Groq حرفه‌ای: {e}")
547
+ continue
548
+
549
+ # استفاده از روش محلی حرفه‌ای
550
+ print(" 🔄 استفاده از روش محلی حرفه‌ای...")
551
+ return self._analyze_with_local_professional(full_text)
552
+
553
+ def _analyze_with_local_professional(self, full_text):
554
+ """تحلیل با روش محلی حرفه‌ای"""
555
+ try:
556
+ result = self.metadata_extractor.extract_metadata_professional(full_text)
557
+ if result['success']:
558
+ metadata = result['metadata']
559
+ formatted_results = {
560
+ "title": metadata.get('title', 'یافت نشد'),
561
+ "author": metadata.get('author', 'یافت نشد'),
562
+ "translator": metadata.get('translator', 'یافت نشد'),
563
+ "publisher": metadata.get('publisher', 'یافت نشد'),
564
+ "publication_year": metadata.get('publication_year', 'یافت نشد'),
565
+ "isbn": metadata.get('isbn', 'یافت نشد'),
566
+ "edition": metadata.get('edition', 'یافت نشد'),
567
+ "subject": 'یافت نشد',
568
+ "summary": 'یافت نشد'
569
+ }
570
+ filled_fields = sum(1 for v in formatted_results.values() if v and v != "یافت نشد")
571
+ return {
572
+ "success": True,
573
+ "source": "local_professional",
574
+ "results": formatted_results,
575
+ "filled_fields": filled_fields,
576
+ "confidence": result['confidence']
577
+ }
578
+ else:
579
+ return {
580
+ "success": False,
581
+ "source": "local_professional",
582
+ "error": result.get('error', 'خطای ناشناخته'),
583
+ "results": self._get_fallback_results()
584
+ }
585
+ except Exception as e:
586
+ return {
587
+ "success": False,
588
+ "source": "local_professional",
589
+ "error": str(e),
590
+ "results": self._get_fallback_results()
591
+ }
592
+
593
+ def _get_fallback_results(self):
594
+ """نتایج پیش‌فرض"""
595
+ return {
596
+ "title": "یافت نشد",
597
+ "author": "یافت نشد",
598
+ "translator": "یافت نشد",
599
+ "publisher": "یافت نشد",
600
+ "publication_year": "یافت نشد",
601
+ "isbn": "یافت نشد",
602
+ "edition": "یافت نشد",
603
+ "subject": "یافت نشد",
604
+ "summary": "یافت نشد"
605
+ }
606
+
607
+ # ============================================================================
608
+ # 🎯 رابط کاربری حرفه‌ای
609
+ # ============================================================================
610
+
611
+ def process_book_professional(file):
612
+ """پردازش کتاب با سیستم حرفه‌ای - ۵ صفحه با DPI 200"""
613
+ if file is None:
614
+ return create_empty_display(), None, "📊 منتظر پردازش..."
615
+
616
+ try:
617
+ processor = ProfessionalPersianBookProcessor()
618
+ print("=" * 60)
619
+ print("🔄 شروع پردازش حرفه‌ای (۵ صفحه اول با DPI 200)...")
620
+ start_time = time.time()
621
+
622
+ # استخراج متن حرفه‌ای - ۵ صفحه با DPI 200
623
+ print("🔍 در حال استخراج متن از ۵ صفحه اول با DPI 200...")
624
+ extracted_text = processor.extract_text_professional(file.name, num_pages=5)
625
+ extraction_time = time.time() - start_time
626
+
627
+ if "❌" in extracted_text:
628
+ return create_error_display(extracted_text), None, f"❌ خطا در استخراج ({extraction_time:.1f}ثانیه)"
629
+ if not extracted_text.strip():
630
+ return create_error_display("متن قابل استخراج یافت نشد"), None, f"⚠️ متن خالی ({extraction_time:.1f}ثانیه)"
631
+
632
+ print(f"�� استخراج متن کامل: {len(extracted_text)} کاراکتر")
633
+ print(f"⏱️ زمان استخراج: {extraction_time:.2f} ثانیه")
634
+
635
+ # تحلیل حرفه‌ای با پرامپت پیشرفته
636
+ print("🔍 در حال تحلیل حرفه‌ای با AI...")
637
+ analysis_start_time = time.time()
638
+ analysis_result = processor.analyze_with_groq_professional(extracted_text)
639
+ analysis_time = time.time() - analysis_start_time
640
+ total_time = extraction_time + analysis_time
641
+
642
+ print(f"⏱️ زمان تحلیل: {analysis_time:.2f} ثانیه")
643
+ print(f"⏱️ زمان کل: {total_time:.2f} ثانیه")
644
+
645
+ # ایجاد گزارش حرفه‌ای
646
+ if analysis_result["success"]:
647
+ print(f"✅ تحلیل موفق با {analysis_result['source']}")
648
+ report = create_professional_report(analysis_result, extracted_text, total_time)
649
+ stats = f"✅ {analysis_result['source']} - {analysis_result['filled_fields']}/۹ فیلد - {total_time:.1f}ثانیه"
650
+ if analysis_result.get('response_length'):
651
+ stats += f" - پاسخ: {analysis_result['response_length']}کاراکتر"
652
+ if analysis_result["source"] == "local_professional":
653
+ stats += f" - اطمینان: {analysis_result.get('confidence', 0)*100:.1f}%"
654
+ else:
655
+ print(f"❌ خطا در تحلیل")
656
+ report = create_error_display(f"خطا در تحلیل: {analysis_result.get('error', 'نامشخص')}")
657
+ stats = f"❌ خطا در تحلیل - {total_time:.1f}ثانیه"
658
+
659
+ # ذخیره فایل
660
+ output_file = "/tmp/book_analysis_professional.txt" # در Hugging Face از /tmp استفاده کنید
661
+ with open(output_file, "w", encoding="utf-8") as f:
662
+ f.write(report)
663
+
664
+ print("✅ پردازش حرفه‌ای ۵ صفحه کامل شد")
665
+ print("=" * 60)
666
+ return create_professional_display(analysis_result, total_time), output_file, stats
667
+
668
+ except Exception as e:
669
+ error_msg = f"❌ خطای سیستمی: {str(e)}"
670
+ print(error_msg)
671
+ return create_error_display(error_msg), None, "❌ خطا"
672
+
673
+ def create_professional_report(analysis_result, full_text, processing_time):
674
+ """ایجاد گزارش حرفه‌ای"""
675
+ results = analysis_result["results"]
676
+ source = analysis_result["source"]
677
+ report = f"""
678
+ 📚 گزارش تحلیل حرفه‌ای کتاب - سیستم پیشرفته
679
+ {'='*50}
680
+ ⚙️ اطلاعات پردازش:
681
+ • روش تحلیل: {source.upper()}
682
+ • زمان پردازش: {processing_time:.2f} ثانیه
683
+ • کیفیت تصویر: DPI 200
684
+ • طول متن: {len(full_text)} کاراکتر
685
+ • فیلدهای پر: {analysis_result['filled_fields']} از ۹
686
+ • صفحات پردازش شده: ۵ صفحه اول
687
+ 📖 اطلاعات استخراج شده:
688
+ {'‐'*30}
689
+ """
690
+
691
+ fields = [
692
+ ('📖 عنوان کتاب', 'title'),
693
+ ('✍️ نویسنده/مؤلف', 'author'),
694
+ ('🌐 مترجم', 'translator'),
695
+ ('🏢 ناشر', 'publisher'),
696
+ ('📅 سال انتشار', 'publication_year'),
697
+ ('🔖 شابک (ISBN)', 'isbn'),
698
+ ('🔄 نوبت چاپ', 'edition'),
699
+ ('📚 موضوع کتاب', 'subject'),
700
+ ('📝 خلاصه محتوا', 'summary')
701
+ ]
702
+
703
+ for persian_name, english_key in fields:
704
+ value = results.get(english_key, 'یافت نشد')
705
+ report += f"{persian_name}: {value}\n"
706
+
707
+ # آمار پیشرفته
708
+ page_count = len([p for p in full_text.split('📄 صفحه') if p.strip()])
709
+ report += f"""
710
+ 📊 آمار حرفه‌ای:
711
+ • صفحات پردازش شده: {page_count} از ۵ صفحه
712
+ • کیفیت استخراج: DPI 200
713
+ • دقت تحلیل: {analysis_result['filled_fields'] * 11.1:.1f}%
714
+ """
715
+ return report
716
+
717
+ def create_professional_display(analysis_result, processing_time):
718
+ """ایجاد نمایش حرفه‌ای"""
719
+ results = analysis_result["results"]
720
+ source = analysis_result["source"]
721
+ filled_fields = analysis_result["filled_fields"]
722
+
723
+ basic_html = ""
724
+ primary_fields = [
725
+ ('title', '📚 عنوان کتاب', 'عنوانی یافت نشد'),
726
+ ('author', '✍️ نویسنده/مؤلف', 'نویسنده‌ای یافت نشد'),
727
+ ('publisher', '🏢 ناشر', 'ناشری یافت نشد'),
728
+ ('publication_year', '📅 سال انتشار', 'سال انتشار یافت نشد'),
729
+ ]
730
+
731
+ for field, display, not_found in primary_fields:
732
+ value = results.get(field, not_found)
733
+ if value != not_found:
734
+ basic_html += f"""
735
+ <div style="background: linear-gradient(135deg, #2a2a2a 0%, #1a3a1a 100%); color: #00ff00; padding: 15px; margin: 8px 0; border-radius: 8px; border-left: 4px solid #00ff00; border-right: 1px solid #00ff00;">
736
+ <strong style="color: #00ff00; font-size: 16px;">{display}:</strong>
737
+ <div style="color: #ffffff; font-size: 15px; margin-top: 5px;">{value}</div>
738
+ </div>
739
+ """
740
+ else:
741
+ basic_html += f"""
742
+ <div style="background: #1a1a1a; color: #666; padding: 15px; margin: 8px 0; border-radius: 8px; border-left: 4px solid #666;">
743
+ <strong style="color: #666;">{display}:</strong> {not_found}
744
+ </div>
745
+ """
746
+
747
+ # فیلدهای تکمیلی
748
+ secondary_html = ""
749
+ secondary_fields = [
750
+ ('translator', '🌐 مترجم', 'مترجمی یافت نشد'),
751
+ ('isbn', '🔖 شابک (ISBN)', 'شابکی یافت نشد'),
752
+ ('edition', '🔄 نوبت چاپ', 'نوبت چاپی یافت نشد'),
753
+ ('subject', '📚 موضوع کتاب', 'موضوعی یافت نشد'),
754
+ ]
755
+
756
+ for field, display, not_found in secondary_fields:
757
+ value = results.get(field, not_found)
758
+ secondary_html += f"""
759
+ <div style="background: #2a2a2a; color: #ccc; padding: 12px; margin: 6px 0; border-radius: 6px; border: 1px solid #444;">
760
+ <strong>{display}:</strong> {value}
761
+ </div>
762
+ """
763
+
764
+ # خلاصه
765
+ summary_html = ""
766
+ summary = results.get('summary', 'یافت نشد')
767
+ if summary != 'یافت نشد':
768
+ summary_html = f"""
769
+ <div style="background: #1a2a1a; color: #aaffaa; padding: 15px; margin: 10px 0; border-radius: 8px; border: 1px solid #00aa00;">
770
+ <strong style="color: #00ff00;">📝 خلاصه محتوا:</strong>
771
+ <div style="color: #e0e0e0; margin-top: 8px; line-height: 1.6;">{summary}</div>
772
+ </div>
773
+ """
774
+
775
+ confidence_html = ""
776
+ if analysis_result.get('confidence'):
777
+ confidence_color = "#00ff00" if analysis_result['confidence'] > 0.7 else "#ffff00" if analysis_result['confidence'] > 0.4 else "#ff4444"
778
+ confidence_html = f"""
779
+ <div style="color: {confidence_color}; font-weight: bold; margin-top: 10px;">
780
+ 🎯 میزان اطمینان تحلیل: {analysis_result['confidence']*100:.1f}%
781
+ </div>
782
+ """
783
+
784
+ return f"""
785
+ <div style="font-family: 'Tahoma', 'Arial', sans-serif; background: #000000; color: #00ff00;">
786
+ <div style="background: linear-gradient(135deg, #001a00 0%, #004400 100%); color: #00ff00; padding: 20px; border-radius: 10px 10px 0 0; border: 2px solid #00ff00;">
787
+ <h2 style="margin: 0; text-align: center; color: #00ff00;">🎯 نتایج تحلیل حرفه‌ای (۵ صفحه اول)</h2>
788
+ <p style="text-align: center; margin: 5px 0 0 0; color: #aaffaa;">کیفیت DPI 200 - پرامپت پیشرفته</p>
789
+ </div>
790
+ <div style="padding: 20px; background: #000000; border-radius: 0 0 10px 10px; border: 2px solid #00ff00; border-top: none;">
791
+ <h3 style="color: #00ff00; border-bottom: 2px solid #00ff00; padding-bottom: 10px;">📖 اطلاعات اصلی کتاب</h3>
792
+ {basic_html}
793
+ <div style="margin-top: 20px;">
794
+ <h4 style="color: #00ff00; margin-bottom: 10px;">📋 اطلاعات تکمیلی</h4>
795
+ {secondary_html}
796
+ </div>
797
+ {summary_html}
798
+ <div style="background: #1a1a1a; color: #00ff00; padding: 15px; border-radius: 8px; margin-top: 20px; border: 2px solid #00ff00;">
799
+ <h4 style="margin-top: 0; color: #00ff00;">🔧 اطلاعات فنی پردازش</h4>
800
+ <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 10px;">
801
+ <div>⏱️ زمان پردازش: <strong style="color: #ffffff;">{processing_time:.2f} ثانیه</strong></div>
802
+ <div>📊 فیلدهای پر: <strong style="color: #ffffff;">{filled_fields} از ۹</strong></div>
803
+ <div>🤖 روش استخراج: <strong style="color: #ffffff;">{source.upper()}</strong></div>
804
+ <div>📄 کیفیت تصویر: <strong style="color: #ffffff;">DPI 200</strong></div>
805
+ <div>🔍 صفحات پردازش: <strong style="color: #ffffff;">۵ صفحه اول</strong></div>
806
+ <div>💾 max_tokens: <strong style="color: #ffffff;">۲۰۰۰</strong></div>
807
+ </div>
808
+ {confidence_html}
809
+ </div>
810
+ </div>
811
+ </div>
812
+ """
813
+
814
+ def create_error_display(error_message):
815
+ """ایجاد نمایش خطا"""
816
+ return f"""
817
+ <div style="background: #1a0000; color: #ff0000; padding: 15px; border-radius: 8px; border: 2px solid #ff0000;">
818
+ <h4 style="margin: 0;">❌ خطا در پردازش</h4>
819
+ <p style="margin: 10px 0 0 0;">{error_message}</p>
820
+ </div>
821
+ """
822
+
823
+ def create_empty_display():
824
+ """ایجاد نمایش خالی"""
825
+ return """
826
+ <div style="background: #000000; color: #00ff00; padding: 30px; text-align: center; border-radius: 8px; border: 2px dashed #00ff00;">
827
+ <h4 style="margin: 0 0 10px 0;">📚 سیستم حرفه‌ای ا��تخراج اطلاعات کتاب</h4>
828
+ <p style="margin: 0;">لطفاً یک فایل PDF یا تصویر آپلود کنید</p>
829
+ <p style="margin: 10px 0 0 0; font-size: 12px; color: #00cc00;">📄 ۵ صفحه اول با DPI 200 پردازش می‌شود</p>
830
+ </div>
831
+ """
832
+
833
+ # ============================================================================
834
+ # 🚀 اجرای سیستم حرفه‌ای
835
+ # ============================================================================
836
+
837
+ print("🎯 ایجاد رابط کاربری حرفه‌ای...")
838
+
839
+ professional_css = """
840
+ .gradio-container {
841
+ background: #000000 !important;
842
+ color: #00ff00 !important;
843
+ }
844
+ .gradio-container .panel {
845
+ background: #000000 !important;
846
+ border: 2px solid #00ff00 !important;
847
+ }
848
+ .gradio-container .button {
849
+ background: linear-gradient(135deg, #001a00 0%, #004400 100%) !important;
850
+ color: #00ff00 !important;
851
+ border: 2px solid #00ff00 !important;
852
+ font-weight: bold !important;
853
+ }
854
+ .gradio-container .button:hover {
855
+ background: linear-gradient(135deg, #003300 0%, #006600 100%) !important;
856
+ }
857
+ """
858
+
859
+ with gr.Blocks(title="سیستم حرفه‌ای استخراج کتاب", theme=gr.themes.Default(primary_hue="green"), css=professional_css) as demo:
860
+ gr.Markdown("""
861
+ <div style="background: linear-gradient(135deg, #001a00 0%, #004400 100%); color: #00ff00; padding: 25px; border-radius: 12px; border: 3px solid #00ff00; font-family: 'Tahoma', sans-serif;">
862
+ <h1 style="text-align: center; margin: 0; color: #00ff00;">📚 سیستم حرفه‌ای استخراج اطلاعات کتاب</h1>
863
+ <p style="text-align: center; color: #aaffaa; margin: 10px 0; font-size: 16px;">پردازش ۵ صفحه اول با کیفیت DPI 200 - پرامپت پیشرفته</p>
864
+ <div style="text-align: center; color: #88ff88; font-size: 14px;">
865
+ • کیفیت تصویر: DPI 200 • max_tokens: 2000 • پردازش ۵ صفحه •
866
+ </div>
867
+ </div>
868
+ """)
869
+
870
+ with gr.Row():
871
+ file_input = gr.File(
872
+ label="📁 آپلود فایل کتاب (PDF یا تصویر)",
873
+ file_types=[".pdf", ".jpg", ".jpeg", ".png"],
874
+ height=100
875
+ )
876
+
877
+ with gr.Row():
878
+ process_btn = gr.Button(
879
+ "🚀 شروع پردازش حرفه‌ای (۵ صفحه با DPI 200)",
880
+ variant="primary",
881
+ size="lg",
882
+ scale=2
883
+ )
884
+
885
+ with gr.Row():
886
+ output_display = gr.HTML(
887
+ label="🎯 نتایج تحلیل حرفه‌ای",
888
+ value=create_empty_display()
889
+ )
890
+
891
+ with gr.Row():
892
+ download_output = gr.File(
893
+ label="📥 دانلود گزارش کامل",
894
+ interactive=False
895
+ )
896
+
897
+ with gr.Row():
898
+ stats_display = gr.Textbox(
899
+ label="📊 وضعیت پردازش حرفه‌ای",
900
+ lines=2,
901
+ interactive=False
902
+ )
903
+
904
+ process_btn.click(
905
+ fn=process_book_professional,
906
+ inputs=[file_input],
907
+ outputs=[output_display, download_output, stats_display]
908
+ )
909
+
910
+ print("✅ سیستم حرفه‌ای با DPI 200 و max_tokens 2000 آماده است!")
911
+ print("🌐 در حال راه‌اندازی سرور Gradio...")
912
+
913
+ # تغییرات برای Hugging Face Spaces
914
+ if __name__ == "__main__":
915
+ demo.launch(
916
+ server_name="0.0.0.0",
917
+ server_port=int(os.environ.get("PORT", 7860)),
918
+ share=False
919
+ )