leilaghomashchi commited on
Commit
9155607
·
verified ·
1 Parent(s): b1dc849

Upload app_rtl_fa_final_UPDATED (6).py

Browse files
Files changed (1) hide show
  1. app_rtl_fa_final_UPDATED (6).py +478 -0
app_rtl_fa_final_UPDATED (6).py ADDED
@@ -0,0 +1,478 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import re
3
+ import os
4
+ import requests
5
+ import json
6
+ import logging
7
+ from typing import Dict, List, Tuple
8
+ from chatgpt_sender import ChatGPTSender # ✅ import ماژول جدید
9
+
10
+ logging.basicConfig(level=logging.INFO)
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class AnonymizerAdvanced:
14
+ """ناشناس‌ساز پیشرفته با روش‌های متعدد"""
15
+
16
+ def __init__(self, cerebras_key: str = None, gpt_key: str = None):
17
+ self.cerebras_key = cerebras_key or os.getenv("CEREBRAS_API_KEY")
18
+ self.gpt_key = gpt_key or os.getenv("OPENAI_API_KEY")
19
+ self.mapping_table = {} # {placeholder: original_text}
20
+ self.reverse_mapping = {} # {original_text: placeholder}
21
+
22
+ # ✅ ایجاد instance از ChatGPTSender
23
+ self.gpt_sender = ChatGPTSender(api_key=self.gpt_key, model="gpt-4o-mini")
24
+
25
+ logger.info("✅ Anonymizer Advanced مقداردهی شد")
26
+
27
+
28
+ def anonymize_with_cerebras(self, text: str) -> Tuple[str, Dict]:
29
+ """ناشناس‌سازی با Cerebras - دریافت mapping از مدل"""
30
+ logger.info("🧠 روش Cerebras...")
31
+
32
+ if not self.cerebras_key:
33
+ logger.error("❌ Cerebras API Key موجود نیست")
34
+ raise ValueError("Cerebras API Key مورد نیاز است")
35
+
36
+ try:
37
+ # مرحله 1: ناشناس‌سازی متن
38
+ prompt1 = f"""متن زیر را ناشناس کنید. قوانین:
39
+ 1. اسامی اشخاص → person-01, person-02, ...
40
+ 2. نام شرکت‌ها/سازمان‌ها → company-01, company-02, ...
41
+ 3. مقادیر پولی → amount-01, amount-02, ...
42
+ 4. درصدها → percent-01, percent-02, ...
43
+ 5. فقط این توکن‌ها استفاده کنید
44
+ 6. شماره‌های نسخه را درست حفظ کنید
45
+ 7. اگر موجودیت تکرار شود از شماره قدیمی استفاده کنید
46
+
47
+ متن:
48
+ {text}
49
+
50
+ خروجی: فقط متن ناشناس شده"""
51
+
52
+ response1 = requests.post(
53
+ "https://api.cerebras.ai/v1/chat/completions",
54
+ headers={
55
+ "Authorization": f"Bearer {self.cerebras_key}",
56
+ "Content-Type": "application/json"
57
+ },
58
+ json={
59
+ "model": "llama-3.3-70b",
60
+ "messages": [{"role": "user", "content": prompt1}],
61
+ "max_tokens": 4096,
62
+ "temperature": 0.1
63
+ },
64
+ timeout=60
65
+ )
66
+
67
+ if response1.status_code != 200:
68
+ logger.error(f"❌ Cerebras Error: {response1.status_code}")
69
+ raise Exception(f"Cerebras API Error: {response1.status_code}")
70
+
71
+ anonymized_text = response1.json()['choices'][0]['message']['content'].strip()
72
+ logger.info("✅ Cerebras: ناشناس‌سازی موفق")
73
+
74
+ # مرحله 2: استخراج mapping از مدل
75
+ prompt2 = f"""متن اصلی:
76
+ {text}
77
+
78
+ متن ناشناس شده:
79
+ {anonymized_text}
80
+
81
+ لطفاً یک جدول mapping برای همه توکن‌های ناشناس ایجاد کن.
82
+ برای هر توکن، متن اصلی کامل آن را مشخص کن.
83
+
84
+ **مهم:**
85
+ - برای person-XX: نام کامل شخص (مثلاً "علی احمدی")
86
+ - برای company-XX: نام کامل شرکت/سازمان (مثلاً "شرکت پتروشیمی")
87
+ - برای amount-XX: عدد + واحد (مثلاً "80 هزار تومان" یا "50 میلیارد ریال")
88
+ - برای percent-XX: عدد + کلمه "درصد" (مثلاً "40 درصد" نه فقط "40")
89
+
90
+ خروجی را به این فرمت JSON بده (فقط JSON، بدون توضیح اضافی):
91
+ {{
92
+ "person-01": "متن اصلی کامل",
93
+ "company-01": "متن اصلی کامل",
94
+ "amount-01": "متن اصلی کامل با واحد",
95
+ "percent-01": "عدد + درصد",
96
+ ...
97
+ }}"""
98
+
99
+ response2 = requests.post(
100
+ "https://api.cerebras.ai/v1/chat/completions",
101
+ headers={
102
+ "Authorization": f"Bearer {self.cerebras_key}",
103
+ "Content-Type": "application/json"
104
+ },
105
+ json={
106
+ "model": "llama-3.3-70b",
107
+ "messages": [{"role": "user", "content": prompt2}],
108
+ "max_tokens": 2048,
109
+ "temperature": 0.1
110
+ },
111
+ timeout=60
112
+ )
113
+
114
+ if response2.status_code == 200:
115
+ mapping_text = response2.json()['choices'][0]['message']['content'].strip()
116
+
117
+ # پاک‌سازی و parse کردن JSON
118
+ # حذف markdown code blocks اگر وجود داشته باشه
119
+ mapping_text = mapping_text.replace('```json', '').replace('```', '').strip()
120
+
121
+ try:
122
+ self.mapping_table = json.loads(mapping_text)
123
+
124
+ # پست-پروسسینگ: اصلاح mapping برای percent ها
125
+ self._fix_percent_mapping()
126
+
127
+ # ساخت reverse mapping
128
+ self.reverse_mapping = {v: k for k, v in self.mapping_table.items()}
129
+ logger.info(f"✅ Mapping استخراج شد: {len(self.mapping_table)} موجودیت")
130
+ except json.JSONDecodeError:
131
+ logger.warning("⚠️ خطا در parse کردن JSON mapping - استفاده از روش fallback")
132
+ self._extract_mapping_from_text(text, anonymized_text)
133
+ else:
134
+ logger.warning("⚠️ خطا در دریافت mapping - استفاده از روش fallback")
135
+ self._extract_mapping_from_text(text, anonymized_text)
136
+
137
+ return anonymized_text, self.mapping_table
138
+
139
+ except Exception as e:
140
+ logger.error(f"❌ Cerebras Exception: {e}")
141
+ raise
142
+
143
+ def _fix_percent_mapping(self):
144
+ """اصلاح mapping برای درصدها و مقادیر - اضافه کردن واحدها اگر فقط عدد باشد"""
145
+ for token, value in self.mapping_table.items():
146
+ value_str = str(value).strip()
147
+
148
+ if token.startswith('percent-'):
149
+ # چک کنیم آیا کلمه "درصد" یا "%" در value هست
150
+ if not re.search(r'(درصد|%|درصدی)', value_str):
151
+ # فقط عدد هست، کلمه "درصد" رو اضافه کن
152
+ self.mapping_table[token] = f"{value_str} درصد"
153
+ logger.info(f"✅ اصلاح {token}: '{value_str}' → '{value_str} درصد'")
154
+
155
+ elif token.startswith('amount-'):
156
+ # چک کنیم آیا واحد پولی در value هست
157
+ if not re.search(r'(میلیارد|میلیون|هزار|تومان|ریال|دلار|یورو|تن)', value_str):
158
+ # فقط عدد هست، احتمالاً باید واحد اضافه بشه
159
+ # اما نمی‌دونیم چه واحدی، پس warning بده
160
+ logger.warning(f"⚠️ {token}: فقط عدد '{value_str}' - واحد مشخص نیست")
161
+
162
+ def _extract_mapping_from_text(self, original: str, anonymized: str):
163
+ """استخراج mapping از متن‌های اصلی و ناشناس شده - نسخه بهبود یافته"""
164
+
165
+ # استخراج همه توکن‌های ناشناس از متن ناشناس‌سازی شده
166
+ all_tokens = []
167
+ for entity_type in ['person', 'company', 'amount', 'percent']:
168
+ tokens = re.findall(f'{entity_type}-\\d+', anonymized)
169
+ all_tokens.extend([(t, entity_type) for t in tokens])
170
+
171
+ # حذف تکراری‌ها و مرتب‌سازی
172
+ all_tokens = sorted(set(all_tokens), key=lambda x: (x[1], int(x[0].split('-')[1])))
173
+
174
+ # الگوهای موجودیت در متن اصلی
175
+ patterns = {
176
+ 'person': r'\b[ء-ي]+\s+[ء-ي]+(?:\s+[ء-ي]+)*\b',
177
+ 'company': r'(?:شرکت|بانک|سازمان|گروه|هلدینگ)\s+[ء-ي]+(?:\s+[ء-ي]+)*',
178
+ 'amount': r'\d+(?:\.\d+)?\s*(?:میلیارد|میلیون|هزار|تومان|ریال|دلار|یورو|تن)',
179
+ 'percent': r'\d+(?:\.\d+)?\s*(?:درصد|%|درصدی)',
180
+ }
181
+
182
+ # استخراج موجودیت‌های اصلی
183
+ original_entities = {}
184
+ for entity_type, pattern in patterns.items():
185
+ matches = list(re.finditer(pattern, original))
186
+ original_entities[entity_type] = [m.group().strip() for m in matches]
187
+
188
+ # نگاشت توکن‌ها به موجودیت‌های اصلی
189
+ for token, entity_type in all_tokens:
190
+ if entity_type in original_entities and original_entities[entity_type]:
191
+ # گرفتن شماره توکن (مثلاً از person-01 عدد 1 رو میگیریم)
192
+ token_num = int(token.split('-')[1]) - 1
193
+
194
+ if token_num < len(original_entities[entity_type]):
195
+ original_text = original_entities[entity_type][token_num]
196
+ self.mapping_table[token] = original_text
197
+ self.reverse_mapping[original_text] = token
198
+ else:
199
+ # اگر شماره توکن بیشتر از تعداد موجودیت‌ها بود
200
+ # از آخرین موجودیت استفاده کن
201
+ original_text = original_entities[entity_type][-1]
202
+ if token not in self.mapping_table:
203
+ self.mapping_table[token] = original_text
204
+ self.reverse_mapping[original_text] = token
205
+
206
+ def analyze_with_gpt(self, anonymized_text: str, analysis_prompt: str = None) -> str:
207
+ """
208
+ ✅ اصلاح شده - استفاده از ماژول ChatGPTSender
209
+ اجرای پرامپت‌های درون متن ناشناس‌سازی شده با ChatGPT
210
+ """
211
+ logger.info("🤖 ChatGPT اجرای پرامپت...")
212
+
213
+ # ✅ اگر پرامپتی نیست، فقط متن ناشناس‌سازی شده برگردان
214
+ if not analysis_prompt or analysis_prompt.strip() == "":
215
+ logger.info("📝 بدون دستورات - فقط متن ناشناس‌سازی شده برگردانده می‌شود")
216
+ return anonymized_text
217
+
218
+ if not self.gpt_key:
219
+ logger.warning("⚠️ GPT API Key نیست")
220
+ return "❌ API Key موجود نیست"
221
+
222
+ try:
223
+ # متن ارسالی شامل متن ناشناس‌سازی شده + دستورات کاربر
224
+ user_message = f"""متن ناشناس‌سازی شده:
225
+ ---
226
+ {anonymized_text}
227
+ ---
228
+
229
+ دستورات:
230
+ {analysis_prompt}
231
+
232
+ توکن‌های ناشناس را حتماً حفظ کن. فقط نتیجه اجرای دستورات را برگردان."""
233
+
234
+ logger.info(f"📋 متن ارسالی به ChatGPT:\n{user_message}\n")
235
+
236
+ # ✅ استفاده از ChatGPTSender به جای requests.post
237
+ system_msg = """شما دستیار اجرای دستورات روی متون ناشناس‌سازی شده‌اید.
238
+ توکن‌های ناشناس (person-01, company-01, amount-01, percent-01) را حتماً حفظ کن.
239
+ فقط دستورات دادشده را اجرا کن."""
240
+
241
+ gpt_response = self.gpt_sender.send(
242
+ text=user_message,
243
+ system_msg=system_msg,
244
+ max_tokens=4096,
245
+ temperature=0.3,
246
+ lang='fa'
247
+ )
248
+
249
+ # بررسی برای خطاهای API
250
+ if gpt_response.startswith("❌"):
251
+ logger.error(f"❌ GPT Error: {gpt_response}")
252
+ return gpt_response
253
+
254
+ logger.info("✅ ChatGPT: دستورات اجرا شدند")
255
+ logger.info(f"📤 پاسخ ChatGPT:\n{gpt_response}\n")
256
+ return gpt_response
257
+
258
+ except Exception as e:
259
+ logger.error(f"❌ GPT Exception: {e}")
260
+ return f"❌ خطا: {str(e)}"
261
+
262
+ def restore_text(self, anonymized_text: str) -> str:
263
+ """بازگردانی متن اصلی"""
264
+ logger.info("🔄 بازگردانی...")
265
+
266
+ restored = anonymized_text
267
+ for placeholder, original in sorted(self.mapping_table.items()):
268
+ restored = restored.replace(placeholder, original)
269
+
270
+ logger.info("✅ بازگردانی کامل")
271
+ return restored
272
+
273
+ def get_mapping_table_md(self) -> str:
274
+ """تبدیل جدول نگاشت به Markdown"""
275
+ if not self.mapping_table:
276
+ return "### 📋 جدول نگاشت\n\nهیچ موجودیتی شناسایی نشد"
277
+
278
+ table = "### 📋 جدول نگاشت\n\n"
279
+ table += "| شناسه | متن اصلی |\n"
280
+ table += "|-------|----------|\n"
281
+
282
+ for token, original in sorted(self.mapping_table.items()):
283
+ table += f"| **{token}** | {original} |\n"
284
+
285
+ return table
286
+
287
+ # متغیر سراسری
288
+ anonymizer = None
289
+
290
+ def process(input_text: str, analysis_prompt: str = None):
291
+ """پردازش متن - 4 مرحله"""
292
+ global anonymizer
293
+
294
+ if not input_text.strip():
295
+ return "", "", "", ""
296
+
297
+ cerebras_key = os.getenv("CEREBRAS_API_KEY")
298
+ gpt_key = os.getenv("OPENAI_API_KEY")
299
+
300
+ if not anonymizer:
301
+ anonymizer = AnonymizerAdvanced(cerebras_key, gpt_key)
302
+ else:
303
+ anonymizer.mapping_table = {}
304
+ anonymizer.reverse_mapping = {}
305
+
306
+ try:
307
+ logger.info("=" * 70)
308
+ logger.info(f"🚀 شروع پردازش - روش: Cerebras")
309
+ logger.info("=" * 70)
310
+
311
+ # ============================================
312
+ # مرحله 1: ناشناس‌سازی
313
+ # ============================================
314
+ logger.info("📝 مرحله 1: ناشناس‌سازی...")
315
+
316
+ anonymized_text, _ = anonymizer.anonymize_with_cerebras(input_text)
317
+
318
+ logger.info(f"✅ ناشناس‌سازی: {len(anonymized_text)} کاراکتر")
319
+
320
+ # ============================================
321
+ # مرحله 2: ChatGPT با متن ناشناس‌سازی شده + دستورات
322
+ # ============================================
323
+ logger.info("🤖 مرحله 2: ChatGPT...")
324
+ gpt_response = anonymizer.analyze_with_gpt(anonymized_text, analysis_prompt)
325
+ logger.info(f"✅ ChatGPT: {len(gpt_response)} کاراکتر")
326
+
327
+ # ============================================
328
+ # مرحله 3: بازگردانی پاسخ ChatGPT
329
+ # ============================================
330
+ logger.info("🔄 مرحله 3: بازگردانی...")
331
+ restored_text = anonymizer.restore_text(gpt_response)
332
+ logger.info("✅ بازگردانی کامل")
333
+
334
+ # ============================================
335
+ # مرحله 4: جدول نگاشت
336
+ # ============================================
337
+ logger.info("📋 مرحله 4: جدول نگاشت...")
338
+ mapping_str = anonymizer.get_mapping_table_md()
339
+ logger.info(f"✅ {len(anonymizer.mapping_table)} موجودیت")
340
+
341
+ logger.info("=" * 70)
342
+ logger.info("✅ تمام مراحل کامل!")
343
+ logger.info("=" * 70)
344
+
345
+ return restored_text, gpt_response, anonymized_text, mapping_str
346
+
347
+ except Exception as e:
348
+ logger.error(f"❌ خطا: {str(e)}", exc_info=True)
349
+ return "", f"❌ خطا: {str(e)}", "", ""
350
+
351
+ def clear_all():
352
+ """پاک کردن همه"""
353
+ return "", "", "", "", "", ""
354
+
355
+ # Gradio Interface
356
+ css_rtl = """
357
+ .input-box { direction: rtl; text-align: right; }
358
+ .textbox textarea { direction: rtl; text-align: right; font-family: 'Tahoma', serif; }
359
+ """
360
+
361
+ with gr.Blocks(title="سیستم ناشناس‌سازی متون", theme=gr.themes.Soft(), css=css_rtl) as app:
362
+
363
+ gr.Markdown("# 🔐 سیستم ناشناس‌سازی متون مالی فارسی", elem_classes="input-box")
364
+
365
+ # ============================================
366
+ # صفحه اول: دکمه‌ها (راست) + ورودی (چپ)
367
+ # ============================================
368
+ with gr.Row():
369
+ # سمت راست: دکمه‌ها و دستورات
370
+ with gr.Column(scale=1):
371
+ analysis_prompt = gr.Textbox(
372
+ lines=8,
373
+ placeholder="",
374
+ label="📋 دستورات ChatGPT (اختیاری)",
375
+ elem_classes="textbox"
376
+ )
377
+
378
+ gr.Markdown("---")
379
+
380
+ with gr.Column():
381
+ process_btn = gr.Button(
382
+ "▶️ پردازش",
383
+ variant="primary",
384
+ size="lg"
385
+ )
386
+
387
+ clear_btn = gr.Button(
388
+ "🗑️ پاک کردن",
389
+ variant="stop",
390
+ size="lg"
391
+ )
392
+
393
+ # سمت چپ: متن ورودی (بزرگ‌تر)
394
+ with gr.Column(scale=3):
395
+ input_text = gr.Textbox(
396
+ lines=14,
397
+ placeholder="متن مالی/خبری را وارد کنید...",
398
+ label="📝 متن ورودی",
399
+ elem_classes="textbox"
400
+ )
401
+
402
+ # ============================================
403
+ # صفحه دوم: 3 باکس نتایج (وسط)
404
+ # ============================================
405
+ gr.Markdown("---")
406
+ gr.Markdown("## 📊 نتایج پردازش", elem_classes="input-box")
407
+
408
+ with gr.Row():
409
+ # باکس 1: متن بازگردانی شده (راست)
410
+ with gr.Column(scale=1):
411
+ restored_text = gr.Textbox(
412
+ lines=12,
413
+ label="✅ متن بازگردانی شده",
414
+ interactive=False,
415
+ elem_classes="textbox"
416
+ )
417
+
418
+ # باکس 2: تحلیل ChatGPT (وسط)
419
+ with gr.Column(scale=1):
420
+ gpt_analysis = gr.Textbox(
421
+ lines=12,
422
+ label="🤖 تحلیل ChatGPT",
423
+ interactive=False,
424
+ elem_classes="textbox"
425
+ )
426
+
427
+ # باکس 3: متن ناشناس‌شده (چپ)
428
+ with gr.Column(scale=1):
429
+ anonymized_text = gr.Textbox(
430
+ lines=12,
431
+ label="🔒 متن ناشناس‌شده",
432
+ interactive=False,
433
+ elem_classes="textbox"
434
+ )
435
+
436
+ # ============================================
437
+ # پایین: جدول نگاشت (Markdown)
438
+ # ============================================
439
+ gr.Markdown("---")
440
+
441
+ mapping_table = gr.Markdown(
442
+ value="### 📋 جدول نگاشت\n\nهنوز پردازشی انجام نشده",
443
+ label="📋 جدول نگاشت",
444
+ elem_classes="input-box"
445
+ )
446
+
447
+ # ============================================
448
+ # Event Handlers
449
+ # ============================================
450
+ process_btn.click(
451
+ fn=process,
452
+ inputs=[input_text, analysis_prompt],
453
+ outputs=[restored_text, gpt_analysis, anonymized_text, mapping_table]
454
+ )
455
+
456
+ clear_btn.click(
457
+ fn=clear_all,
458
+ outputs=[input_text, analysis_prompt, restored_text, gpt_analysis, anonymized_text, mapping_table]
459
+ )
460
+
461
+ if __name__ == "__main__":
462
+ print("=" * 70)
463
+ print("🚀 سیستم ناشناس‌سازی متون در حال راه‌اندازی...")
464
+ print("=" * 70)
465
+ print("\n📋 نحوه استفاده:\n")
466
+ print("1. CEREBRAS_API_KEY و OPENAI_API_KEY را تنظیم کنید")
467
+ print("2. http://localhost:7860 را باز کنید")
468
+ print("3. متن را وارد کنید")
469
+ print("4. 'پردازش' را کلیک کنید\n")
470
+ print("روش استفاده شده: Cerebras (Llama 3.3-70B)")
471
+ print("=" * 70 + "\n")
472
+
473
+ app.launch(
474
+ server_name="0.0.0.0",
475
+ server_port=7860,
476
+ share=False,
477
+ show_error=True
478
+ )