leilaghomashchi commited on
Commit
578a618
·
verified ·
1 Parent(s): bd9c465

Upload app_enhanced__1_ (1).py

Browse files
Files changed (1) hide show
  1. app_enhanced__1_ (1).py +487 -0
app_enhanced__1_ (1).py ADDED
@@ -0,0 +1,487 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import re
3
+ import os
4
+ import requests
5
+ import logging
6
+ from typing import Dict, List, Tuple, Set
7
+ import json
8
+
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class AnonymizerCerebrasEnhanced:
13
+ def __init__(self, api_key: str = None):
14
+ self.api_key = api_key or os.getenv("CEREBRAS_API_KEY")
15
+ self.mapping_table = {}
16
+ self.counters = {
17
+ 'company': 0, 'person': 0, 'amount': 0, 'phone': 0,
18
+ 'email': 0, 'id_number': 0, 'date': 0, 'location': 0,
19
+ 'percent': 0
20
+ }
21
+ self.seen_entities = {} # برای ثبات نگاشت
22
+
23
+ if not self.api_key:
24
+ raise ValueError("❌ کلید API Cerebras یافت نشد!")
25
+
26
+ logger.info("✅ Anonymizer Enhanced مقداردهی شد")
27
+
28
+ def get_system_prompt(self) -> str:
29
+ """ایجاد دستورالعمل سیستمی پیشرفته برای Groq"""
30
+ return """شما یک «ناشناس‌ساز متون مالی/خبری فارسی» هستید. وظیفه‌تان جایگزینی اسامی خاص و مقادیر عددی با شناسه‌های بی‌معناست.
31
+
32
+ ## **قوانین اندیس‌گذاری - CRITICAL**
33
+ ### **1. ترتیب شماره‌گذاری الزامی:**
34
+ - شرکت‌ها: company-01, company-02, company-03, company-04, ... (پیوسته و بدون گپ)
35
+ - اشخاص: person-01, person-02, person-03, ... (پیوسته و بدون گپ)
36
+ - اعداد: amount-01, amount-02, amount-03, ... (پیوسته و بدون گپ)
37
+ - درصدها: percent-01, percent-02, percent-03, ... (پیوسته و بدون گپ)
38
+
39
+ ### **2. ثبات شناسه‌ها در متن:**
40
+ - اگر "همراه اول" اول‌بار company-01 شد، در تمام متن همان باشد
41
+ - اگر "مهدی احمدی" اول‌بار person-01 شد، در تمام متن همان باشد
42
+
43
+ ### **3. تشخیص صحیح انواع:**
44
+ **شرکت/سازمان:** همراه اول، بانک ملی، ایران‌خودرو، سایپا، بانک مرکزی، سامانه کدال، وزارت نفت
45
+ **شخص:** مهدی اخوان بهابادی، محمدرضا فرزین، ابوالفضل نجارزاده
46
+ **عدد:** 37، 70، 677، 73.7، 178 (هر عددی)
47
+ **درصد:** 37 درصدی، 15 درصدی، 53 درصد، 43%
48
+
49
+ ## **انواع موجودیت‌ها:**
50
+
51
+ **company-XX:** نام شرکت‌ها، سازمان‌ها، بانک‌ها، هلدینگ‌ها، گروه‌های مالی (مثال: ایران خودرو، بانک ملی، گروه مالی صبا، سازمان حسابرسی)
52
+
53
+ **person-XX:** نام و نام خانوادگی اشخاص (مثال: محمد رضایی، مهدی اخوان بهابادی، فرج‌اله قدمی)
54
+
55
+ **amount-XX:** مبالغ مالی شامل ریال، تومان، همت، دلار، تن، دستگاه و واحدهای اندازه‌گیری (مثال: ۲۳ هزار و ۲۹۶ میلیارد تومان، ۵۰۰ میلیون دلار، ۷۳.۷ میلیون نفر، 636 ریال)
56
+
57
+ **percent-XX:** درصدها و نسبت‌ها (مثال: ۴.۵۸ درصد، ۷۵ درصد، ۱۴٪، منفی 345 درصد)
58
+
59
+ ## **قوانین کلیدی:**
60
+
61
+ 1. **ترتیب شماره‌گذاری:** اولین باری که موجودیت ظاهر می‌شود، شماره می‌گیرد (01، 02، 03، ...)
62
+
63
+ 2. **حفظ هویت یکسان:** اگر همان موجودیت دوباره آمد، از همان شماره استفاده کن. مثلا "ایران خودرو" در جمله اول و "این شرکت" در جمله دوم هر دو company-01 هستند.
64
+
65
+ 3. **تشخیص نام‌های مختلف:** "فولاد مبارکه اصفهان" و "فولاد مبارکه" و "این شرکت" همه company-01 هستند. "همراه اول" و "گروه همراه اول" و "این اپراتور" همه company-01 هستند. اما "بانک پاسارگاد" و "سرزمین هوشمند پاد" دو company مختلف هستند.
66
+
67
+ 4. **مبالغ و درصدهای مختلف:** هر عدد جدید، شماره جدید می‌گیرد
68
+
69
+ 5. **حفظ ساختار:** ساختار جمله را حفظ کن، تاریخ‌ها را تغییر نده، کلمات توصیفی مثل "شرکت"، "بانک"، "گروه" را قبل از برچسب حفظ کن
70
+
71
+ 6. **هیچ توضیح اضافه‌ای نده:** فقط متن ناشناس‌شده را برگردان، بدون توضیح یا تفسیر
72
+
73
+ ## **تشخیص دقیق درصدها:**
74
+ - "37 درصدی" → percent-01 (نه amount)
75
+ - "15 درصد" → percent-02 (نه amount)
76
+ - "53%" → percent-03 (نه amount)
77
+ - "بازه 10 تا 20 درصد" → percent-04 تا percent-05
78
+
79
+ ## **موارد حفظ شده:**
80
+ - تاریخ‌ها: 1404/04/23، 30 آذر 1403، پاییز 1401
81
+ - عناوین شغلی: مدیرعامل، رئیس کل، مدیرکل
82
+ - واحدها: میلیارد تومان، همت، ریال، ماه، سال
83
+ - مکان‌ها: تهران، اصفهان، ایران
84
+
85
+ ## **ممنوع:**
86
+ - کلمات انگلیسی اضافی
87
+ - تغییر ساختار جمله
88
+ - حذف یا اضافه کردن کلمات
89
+
90
+ ## **نمونه‌های آموزشی:**
91
+
92
+ **نمونه ۱:**
93
+ ورودی: مهدی اخوان بهابادی، مدیرعامل همراه اول، اعلام کرد درآمد عملیاتی شرکت با رشد 37 درصدی به 70 هزار و 677 میلیارد تومان رسیده است. سود خالص 7101 میلیارد تومان و تلفیقی گروه همراه اول 8003 میلیارد تومان شد.
94
+ خروجی: person-01، مدیرعامل company-01، اعلام کرد درآمد عملیاتی شرکت با رشد percent-01 به amount-01 رسیده است. سود خالص amount-02 و تلفیقی گروه company-01 amount-03 شد.
95
+
96
+ **نمونه ۲:**
97
+ ورودی: بانک مرکزی و بانک ملی با همکاری محمدرضا فرزین، 60 درصد سپرده‌ها را مدیریت کردند.
98
+ خروجی: company-01 و company-02 با همکاری person-01، percent-01 سپرده‌ها را مدیریت کردند.
99
+
100
+ **نمونه ۳:**
101
+ ورودی: سایپا و ایران‌خودرو مجموع زیان 620 همت داشتند و سایپا 269 هزار میلیارد زیان اعلام کرد.
102
+ خروجی: company-01 و company-02 مجموع زیان amount-01 داشتند و company-01 amount-02 زیان اعلام کرد.
103
+
104
+ **نمونه ۴:**
105
+ ورودی: ایران خودرو در اسفندماه سال 1402 حدود 23 هزار و 296 میلیارد تومان درآمد کسب کرد که در مقایسه با بهمن 4.58 درصد افزایش داشت. زیان خالص ایران خودرو در این سال به بیش از 37 همت رساند.
106
+ خروجی: company-01 در اسفندماه سال 1402 حدود amount-01 درآمد کسب کرد که در مقایسه با بهمن percent-01 افزایش داشت. زیان خالص company-01 در این سال به بیش از amount-02 رساند.
107
+
108
+ **نمونه ۵:**
109
+ ورودی: بانک پاسارگاد با شناسایی سود خالص 155 هزار میلیارد ریالی در رده دوم سودآورترین بانک‌های کشور قرار گرفت. در مقابل، بانک سرمایه با مدیرعاملی فرج‌اله قدمی وضعیت بحرانی دارد.
110
+ خروجی: company-01 با شناسایی سود خالص amount-01 در رده دوم سودآورترین بانک‌های کشور قرار گرفت. در مقابل، company-02 با مدیرعاملی person-01 وضعیت بحرانی دارد.
111
+
112
+ **نمونه ۶:**
113
+ ورودی: مجمع عمومی عادی سالیانه شرکت پتروشیمی پارس با نماد «پارس» راس ساعت 10:00 روز سه شنبه مورخ 1403/04/12 برگزار شد و سازمان حسابرسی به‌ عنوان بازرس قانونی انتخاب شد.
114
+ خروجی: مجمع عمومی عادی سالیانه company-01 با نماد «پارس» راس ساعت 10:00 روز سه شنبه مورخ 1403/04/12 برگزار شد و company-02 به‌ عنوان بازرس قانونی انتخاب شد.
115
+
116
+ **نمونه ۷:**
117
+ ورودی: شرکت فولاد مبارکه اصفهان در مجمع عمومی عادی سالیانه حاشیه سود خالص شرکت 38 درصد بوده و EPS این شرکت از 636 ریال به 614 ریال خواهد رسید.
118
+ خروجی: company-01 در مجمع عمومی عادی سالیانه حاشیه سود خالص شرکت percent-01 بوده و EPS این شرکت از amount-01 به amount-02 خواهد رسید.
119
+
120
+ **نمونه ۸:**
121
+ ورودی: بانک سرمایه با مدیرعاملی فرج‌اله قدمی زیان خالص 2700 میلیارد تومانی در سه‌ماهه نخست 1404 گزارش کرد. نسبت کفایت سرمایه به منفی 345 درصد رسیده و زیان انباشته نزدیک به 67 هزار میلیارد تومان است.
122
+ خروجی: company-01 با مدیرعاملی person-01 زیان خالص amount-01 در سه‌ماهه نخست 1404 گزارش کرد. نسبت کفایت سرمایه به percent-01 رسیده و زیان انباشته نزدیک به amount-02 است.
123
+
124
+ **نمونه ۹:**
125
+ ورودی: دو بانک ملت و پاسارگاد به ترتیب با شناسایی سود خالص 157 و 155 هزار میلیارد ریالی رقابت تنگاتنگی داشته و در رده‌های اول و دوم جای دارند.
126
+ خروجی: دو بانک company-01 و company-02 به ترتیب با شناسایی سود خالص amount-01 و amount-02 رقابت تنگاتنگی داشته و در رده‌های اول و دوم جای دارند.
127
+
128
+ **نمونه ۱۰:**
129
+ ورودی: مرور صورت‌های مالی بانک‌ها نشان می‌دهد سهم سودهای ارزی به‌راحتی به 40–60٪ رسیده است و این مسئله نشان‌دهنده وضعیت غیرعادی بازار است.
130
+ خروجی: مرور صورت‌های مالی بانک‌ها نشان می‌دهد سهم سودهای ارزی به‌راحتی به percent-01 رسیده است و این مسئله نشان‌دهنده وضعیت غیرعادی بازار است.
131
+
132
+ **فقط متن ناشناس‌شده را برگردان - هیچ توضیح اضافی نیاز نیست."""
133
+
134
+ def get_user_prompt(self, text: str) -> str:
135
+ """تشکیل پرامپت کاربر"""
136
+ return f"""متن مالی فارسی زیر را تجزیه و تحلیل کنید. تمام موجودیت‌های حساس را شناسایی کنید و یک JSON Array برگردانید.
137
+
138
+ متن:
139
+ {text}
140
+
141
+ **مهم**:
142
+ - اگر چند بار یک نام تکرار شود، یک id بدهید
143
+ - کلمات عمومی را حفظ کنید
144
+ - واحدها را حفظ کنید
145
+ - فقط JSON برگردانید!
146
+
147
+ یک JSON Array برگردانید. هر عنصر دارای:
148
+ - "text": متن دقیق استخراج شده
149
+ - "type": نوع (company, person, amount, percent, phone, email, date, location, id_number)
150
+ - "original": توضیح اضافی اگر نام مستعار باشد"""
151
+
152
+ def call_cerebras(self, text: str) -> List[Dict]:
153
+ """فراخوانی Cerebras API با پرامپت بهبود شده"""
154
+ logger.info("🔄 فراخوانی Cerebras API با دستورالعمل قوی...")
155
+
156
+ system_prompt = self.get_system_prompt()
157
+ user_prompt = self.get_user_prompt(text)
158
+
159
+ try:
160
+ response = requests.post(
161
+ "https://api.cerebras.ai/v1/chat/completions",
162
+ headers={
163
+ "Authorization": f"Bearer {self.api_key}",
164
+ "Content-Type": "application/json"
165
+ },
166
+ json={
167
+ "model": "llama-3.3-70b",
168
+ "messages": [
169
+ {"role": "system", "content": system_prompt},
170
+ {"role": "user", "content": user_prompt}
171
+ ],
172
+ "max_tokens": 4000,
173
+ "temperature": 0.1
174
+ },
175
+ timeout=30
176
+ )
177
+
178
+ if response.status_code != 200:
179
+ logger.error(f"❌ خطای API Cerebras: {response.text}")
180
+ return []
181
+
182
+ result = response.json()
183
+ content = result['choices'][0]['message']['content']
184
+
185
+ try:
186
+ # تمیز کردن محتوا از markdown اگر وجود داشته باشد
187
+ content = content.replace("```json", "").replace("```", "").strip()
188
+ entities = json.loads(content)
189
+ if not isinstance(entities, list):
190
+ entities = []
191
+ logger.info(f"✅ {len(entities)} موجودیت استخراج شد")
192
+ return entities
193
+ except json.JSONDecodeError:
194
+ logger.error(f"❌ خطا در JSON parsing: {content[:200]}")
195
+ return []
196
+
197
+ except Exception as e:
198
+ logger.error(f"❌ خطا Cerebras: {e}")
199
+ return []
200
+
201
+ def get_placeholder(self, entity_type: str) -> str:
202
+ """تولید placeholder با format جدید"""
203
+ type_lower = entity_type.lower()
204
+ if type_lower not in self.counters:
205
+ type_lower = 'amount'
206
+
207
+ self.counters[type_lower] += 1
208
+ return f"{type_lower}-{self.counters[type_lower]:02d}"
209
+
210
+ def anonymize(self, text: str) -> Tuple[str, List]:
211
+ """ناشناس‌سازی متن با قوانین ثبات"""
212
+ logger.info("🚀 شروع ناشناس‌سازی متن...")
213
+
214
+ # تنظیف
215
+ self.mapping_table = {}
216
+ self.seen_entities = {}
217
+ for key in self.counters:
218
+ self.counters[key] = 0
219
+
220
+ # دریافت موجودیت‌ها
221
+ entities = self.call_cerebras(text)
222
+
223
+ if not entities:
224
+ logger.warning("⚠️ موجودیتی شناسایی نشد")
225
+ return text, []
226
+
227
+ logger.info("🔄 Processing entities...")
228
+
229
+ # جایگزینی با قانون ثبات
230
+ anonymized = text
231
+ replacements = []
232
+
233
+ for entity in entities:
234
+ entity_type = entity.get('type', 'amount').lower()
235
+ entity_text = entity.get('text', '').strip()
236
+ original_info = entity.get('original', '')
237
+
238
+ if not entity_text:
239
+ continue
240
+
241
+ # بررسی اگر این موجودیت قبلاً دیده شده است
242
+ entity_key = (entity_type, entity_text.lower())
243
+
244
+ if entity_key in self.seen_entities:
245
+ token = self.seen_entities[entity_key]
246
+ logger.info(f"🔄 موجودیت تکراری: {entity_text} → {token}")
247
+ else:
248
+ token = self.get_placeholder(entity_type)
249
+ self.seen_entities[entity_key] = token
250
+ self.mapping_table[token] = {
251
+ 'original': entity_text,
252
+ 'type': entity_type,
253
+ 'note': original_info
254
+ }
255
+ logger.info(f"✅ جایگزینی: {entity_text} → {token}")
256
+
257
+ # جایگزینی دقیق (case-sensitive اول، سپس case-insensitive)
258
+ idx = anonymized.find(entity_text)
259
+ if idx != -1:
260
+ anonymized = anonymized[:idx] + token + anonymized[idx + len(entity_text):]
261
+ replacements.append({
262
+ 'original': entity_text,
263
+ 'placeholder': token,
264
+ 'type': entity_type
265
+ })
266
+
267
+ logger.info(f"✅ ناشناس‌سازی کامل - {len(self.mapping_table)} نگاشت")
268
+ return anonymized, entities
269
+
270
+ def get_mapping_table_str(self) -> str:
271
+ """جدول نگاشت جزئی"""
272
+ if not self.mapping_table:
273
+ return "❌ موجودیتی شناسایی نشد"
274
+
275
+ result = "## 📊 جدول نگاشت\n\n"
276
+ result += "| توکن | اطلاعات اصلی | نوع |\n"
277
+ result += "|------|--------|------|\n"
278
+
279
+ for token, info in sorted(self.mapping_table.items()):
280
+ entity_type = info.get('type', 'unknown')
281
+ original = info.get('original', '')
282
+ note = info.get('note', '')
283
+
284
+ note_str = f" ({note})" if note else ""
285
+ result += f"| `{token}` | {original}{note_str} | {entity_type} |\n"
286
+
287
+ return result
288
+
289
+ def restore(self, text: str) -> str:
290
+ """بازگردانی اطلاعات اصلی"""
291
+ logger.info("🔄 بازگردانی اطلاعات...")
292
+ restored = text
293
+ for token, info in self.mapping_table.items():
294
+ original = info.get('original', '')
295
+ restored = restored.replace(token, original)
296
+ logger.info("✅ بازگردانی کامل")
297
+ return restored
298
+
299
+
300
+ # متغیرهای global
301
+ anonymizer = None
302
+
303
+ def process(input_text: str) -> Tuple[str, str, str, str, str]:
304
+ """
305
+ روند کامل:
306
+ 1. ناشناس‌سازی با Cerebras (llama-3.3-70b) + پرامپت قوی
307
+ 2. ارسال به ChatGPT (حتما!)
308
+ 3. بازگردانی پاسخ ChatGPT
309
+ """
310
+ global anonymizer
311
+
312
+ try:
313
+ if not input_text.strip():
314
+ return "", "", "", "", ""
315
+
316
+ # دریافت API Keys
317
+ api_key_cerebras = os.getenv("CEREBRAS_API_KEY")
318
+ api_key_gpt = os.getenv("OPENAI_API_KEY")
319
+
320
+ if not api_key_gpt:
321
+ logger.error("❌ OPENAI_API_KEY یافت نشد")
322
+ return "", "", "", "", ""
323
+
324
+ if not api_key_cerebras:
325
+ logger.error("❌ CEREBRAS_API_KEY یافت نشد")
326
+ return "", "", "", "", ""
327
+
328
+ # ============================================
329
+ # مرحله 1: مقداردهی
330
+ # ============================================
331
+ if not anonymizer:
332
+ logger.info("Initializing anonymizer...")
333
+ anonymizer = AnonymizerCerebrasEnhanced()
334
+
335
+ # ============================================
336
+ # مرحله 2: ناشناس‌سازی با پرامپت قوی
337
+ # ============================================
338
+ logger.info("Step 1: Anonymizing text with Cerebras...")
339
+
340
+ anonymized_text, entities = anonymizer.anonymize(input_text)
341
+
342
+ if not entities:
343
+ logger.warning("⚠️ موجودیتی شناسایی نشد - متن ناشناس نشد")
344
+ return input_text, "", "", "", ""
345
+
346
+ # ============================================
347
+ # مرحله 3: جدول نگاشت
348
+ # ============================================
349
+ logger.info("Step 2: Creating mapping table")
350
+ mapping = anonymizer.get_mapping_table_str()
351
+ logger.info(f"📋 {len(anonymizer.mapping_table)} نگاشت ایجاد شد")
352
+
353
+ # ============================================
354
+ # مرحله 4: ارسال به ChatGPT (حتما!)
355
+ # ============================================
356
+ logger.info("Step 3: Sending to ChatGPT...")
357
+
358
+ prompt = f"""متن ناشناس‌شده زیر (متن مالی) را تحلیل و خلاصه کنید.
359
+
360
+ متن:
361
+ {anonymized_text}
362
+
363
+ لطفاً:
364
+ 1. خلاصه‌ای مختصر و معنادار ارائه دهید
365
+ 2. نکات اصلی را مشخص کنید
366
+ 3. تمام توکن‌های ناشناس (مثل company-01، amount-02) را حفظ کنید
367
+ 4. تنها اطلاعات موجود در متن را بیان کنید"""
368
+
369
+ logger.info(f"📤 ارسال به ChatGPT (gpt-4o-mini)...")
370
+
371
+ try:
372
+ gpt_response_obj = requests.post(
373
+ "https://api.openai.com/v1/chat/completions",
374
+ headers={"Authorization": f"Bearer {api_key_gpt}"},
375
+ json={
376
+ "model": "gpt-4o-mini",
377
+ "messages": [
378
+ {
379
+ "role": "system",
380
+ "content": "شما دستیار تحلیل متون مالی فارسی هستید. متن‌های ناشناس‌شده را دقیق تحلیل کنید. تمام توکن‌های ناشناس را حفظ کنید."
381
+ },
382
+ {"role": "user", "content": prompt}
383
+ ],
384
+ "max_tokens": 1500,
385
+ "temperature": 0.7
386
+ },
387
+ timeout=30
388
+ )
389
+
390
+ if gpt_response_obj.status_code == 200:
391
+ gpt_response = gpt_response_obj.json()['choices'][0]['message']['content']
392
+ logger.info("✅ پاسخ دریافت شد")
393
+ else:
394
+ error_text = gpt_response_obj.json().get('error', {}).get('message', gpt_response_obj.text)
395
+ logger.error(f"❌ خطای ChatGPT: {error_text}")
396
+ return input_text, anonymized_text, "", "", mapping
397
+
398
+ except Exception as e:
399
+ logger.error(f"❌ خطا در ارسال به ChatGPT: {e}")
400
+ return input_text, anonymized_text, "", "", mapping
401
+
402
+ # ============================================
403
+ # مرحله 5: بازگردانی پاسخ ChatGPT
404
+ # ============================================
405
+ logger.info("Step 4: Restoring original text...")
406
+
407
+ restored_text = anonymizer.restore(gpt_response)
408
+
409
+ logger.info(f"✅ بازگردانی کامل")
410
+
411
+ logger.info(f"Done. Input: {len(input_text)} | Anonymized: {len(anonymized_text)} | Entities: {len(entities)}")
412
+
413
+ return input_text, anonymized_text, gpt_response, restored_text, mapping
414
+
415
+ except Exception as e:
416
+ logger.error(f"❌ خطا عمومی: {e}", exc_info=True)
417
+ return "", "", "", "", ""
418
+
419
+ def clear():
420
+ """پاک کردن"""
421
+ return "", "", "", "", ""
422
+
423
+ # رابط Gradio
424
+ with gr.Blocks(title="Text Anonymization", theme=gr.themes.Soft()) as app:
425
+
426
+ with gr.Row():
427
+ with gr.Column(scale=2):
428
+ input_text = gr.Textbox(
429
+ lines=12,
430
+ placeholder="متن را وارد کنید...",
431
+ label="Input"
432
+ )
433
+
434
+ with gr.Column(scale=1):
435
+ process_btn = gr.Button("Process", variant="primary", size="lg")
436
+ clear_btn = gr.Button("Clear", variant="stop")
437
+
438
+ with gr.Row():
439
+ with gr.Column():
440
+ anonymized_text = gr.Textbox(
441
+ lines=10,
442
+ label="Anonymized",
443
+ interactive=False
444
+ )
445
+
446
+ with gr.Column():
447
+ gpt_response = gr.Textbox(
448
+ lines=10,
449
+ label="GPT Response",
450
+ interactive=False
451
+ )
452
+
453
+ with gr.Column():
454
+ restored_text = gr.Textbox(
455
+ lines=10,
456
+ label="Restored",
457
+ interactive=False
458
+ )
459
+
460
+ with gr.Row():
461
+ with gr.Column():
462
+ mapping = gr.Textbox(
463
+ lines=10,
464
+ label="Mapping",
465
+ interactive=False
466
+ )
467
+
468
+ # Event handlers
469
+ process_btn.click(
470
+ fn=process,
471
+ inputs=[input_text],
472
+ outputs=[input_text, anonymized_text, gpt_response, restored_text, mapping]
473
+ )
474
+
475
+ clear_btn.click(
476
+ fn=clear,
477
+ outputs=[input_text, anonymized_text, gpt_response, restored_text, mapping]
478
+ )
479
+
480
+ if __name__ == "__main__":
481
+ print("Starting Text Anonymization System...")
482
+ app.launch(
483
+ server_name="0.0.0.0",
484
+ server_port=7860,
485
+ share=False,
486
+ show_error=True
487
+ )