leilaghomashchi commited on
Commit
1df4886
·
verified ·
1 Parent(s): e2948b0

Upload app_rtl_fa_final_UPDATED (5).py

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