KashefTech commited on
Commit
878645f
·
verified ·
1 Parent(s): 48eaf65

Upload app_clean.py

Browse files
Files changed (1) hide show
  1. app_clean.py +777 -0
app_clean.py ADDED
@@ -0,0 +1,777 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, Optional
8
+ from llm_sender_unified import create_llm_sender, AVAILABLE_MODELS
9
+
10
+ logging.basicConfig(level=logging.INFO)
11
+ logger = logging.getLogger(__name__)
12
+
13
+ class AnonymizerAdvanced:
14
+ """ناشناس‌ساز پیشرفته با روش‌های متعدد"""
15
+
16
+ def __init__(
17
+ self,
18
+ cerebras_key: str = None,
19
+ llm_provider: str = "chatgpt",
20
+ llm_model: str = None,
21
+ llm_api_key: str = None,
22
+ entities_to_anonymize: List[str] = None
23
+ ):
24
+ self.cerebras_key = cerebras_key or os.getenv("CEREBRAS_API_KEY")
25
+ self.llm_provider = llm_provider
26
+ self.llm_model = llm_model
27
+ self.llm_api_key = llm_api_key
28
+ self.entities_to_anonymize = entities_to_anonymize or ["person", "company", "amount", "percent"]
29
+ self.mapping_table = {}
30
+ self.reverse_mapping = {}
31
+
32
+ # ایجاد LLM sender
33
+ self._create_llm_sender()
34
+
35
+ logger.info(f"✅ Anonymizer Advanced مقداردهی شد با {llm_provider}")
36
+
37
+ def _create_llm_sender(self):
38
+ """ایجاد LLM sender مناسب"""
39
+ try:
40
+ # ✅ تصمیم‌گیری برای API key
41
+ if self.llm_provider == "chatgpt" and self.llm_model == "gpt-4o-mini":
42
+ # فقط برای gpt-4o-mini از secret بخوان
43
+ api_key = os.getenv("OPENAI_API_KEY")
44
+ logger.info("🔑 استفاده از API key از Secret برای gpt-4o-mini")
45
+ else:
46
+ # برای بقیه مدل‌ها از input کاربر
47
+ api_key = self.llm_api_key
48
+ logger.info("🔑 استفاده از API key ورودی کاربر")
49
+
50
+ # ایجاد sender
51
+ self.llm_sender = create_llm_sender(
52
+ provider=self.llm_provider,
53
+ api_key=api_key,
54
+ model=self.llm_model
55
+ )
56
+
57
+ logger.info(f"✅ LLM Sender ایجاد شد: {self.llm_provider} - {self.llm_sender.model}")
58
+
59
+ except Exception as e:
60
+ logger.error(f"❌ خطا در ایجاد LLM Sender: {e}")
61
+ # fallback to ChatGPT
62
+ self.llm_sender = create_llm_sender("chatgpt")
63
+
64
+ def set_llm_provider(self, provider: str, model: str = None, api_key: str = None, entities: List[str] = None):
65
+ """تغییر provider و مدل LLM و موجودیت‌های ناشناس‌سازی"""
66
+ self.llm_provider = provider
67
+ self.llm_model = model
68
+ self.llm_api_key = api_key
69
+ if entities is not None:
70
+ self.entities_to_anonymize = entities
71
+ self._create_llm_sender()
72
+ logger.info(f"✅ LLM تغییر یافت به: {provider} - {model}")
73
+ logger.info(f"✅ موجودیت‌های ناشناس‌سازی: {self.entities_to_anonymize}")
74
+
75
+ def anonymize_with_cerebras(self, text: str) -> Tuple[str, Dict]:
76
+ """ناشناس‌سازی با Cerebras - بر اساس موجودیت‌های انتخابی"""
77
+ logger.info("🧠 روش Cerebras...")
78
+
79
+ if not self.cerebras_key:
80
+ logger.error("❌ Cerebras API Key موجود نیست")
81
+ raise ValueError("Cerebras API Key مورد نیاز است")
82
+
83
+ # ✅ ساخت دستورات بر اساس موجودیت‌های انتخابی
84
+ instructions = []
85
+ instruction_number = 1
86
+
87
+ if "person" in self.entities_to_anonymize:
88
+ instructions.append(f"{instruction_number}. اسامی اشخاص → person-01, person-02, ...")
89
+ instruction_number += 1
90
+
91
+ if "company" in self.entities_to_anonymize:
92
+ instructions.append(f"{instruction_number}. نام شرکت‌ها/سازمان‌ها → company-01, company-02, ...")
93
+ instruction_number += 1
94
+
95
+ if "amount" in self.entities_to_anonymize:
96
+ instructions.append(f"{instruction_number}. مقادیر پولی → amount-01, amount-02, ...")
97
+ instruction_number += 1
98
+
99
+ if "percent" in self.entities_to_anonymize:
100
+ instructions.append(f"{instruction_number}. درصدها → percent-01, percent-02, ...")
101
+ instruction_number += 1
102
+
103
+ # اگه هیچی انتخاب نشده، متن رو همون‌طور برگردون
104
+ if not instructions:
105
+ logger.warning("⚠️ هیچ موجودیتی برای ناشناس‌سازی انتخاب نشده!")
106
+ return text, {}
107
+
108
+ instructions_text = "\n".join(instructions)
109
+ instructions_text += f"\n{instruction_number}. فقط این توکن‌ها استفاده کنید"
110
+ instructions_text += f"\n{instruction_number + 1}. شماره‌های نسخه را درست حفظ کنید"
111
+ instructions_text += f"\n{instruction_number + 2}. اگر موجودیت تکرار شود از شماره قدیمی استفاده کنید"
112
+
113
+ try:
114
+ # مرحله 1: ناشناس‌سازی متن
115
+ prompt1 = f"""متن زیر را ناشناس کنید. قوانین:
116
+ {instructions_text}
117
+
118
+ متن:
119
+ {text}
120
+
121
+ خروجی: فقط متن ناشناس شده"""
122
+
123
+ response1 = requests.post(
124
+ "https://api.cerebras.ai/v1/chat/completions",
125
+ headers={
126
+ "Authorization": f"Bearer {self.cerebras_key}",
127
+ "Content-Type": "application/json"
128
+ },
129
+ json={
130
+ "model": "llama-3.3-70b",
131
+ "messages": [{"role": "user", "content": prompt1}],
132
+ "max_tokens": 4096,
133
+ "temperature": 0.1
134
+ },
135
+ timeout=60
136
+ )
137
+
138
+ if response1.status_code != 200:
139
+ logger.error(f"❌ Cerebras Error: {response1.status_code}")
140
+ raise Exception(f"Cerebras API Error: {response1.status_code}")
141
+
142
+ anonymized_text = response1.json()['choices'][0]['message']['content'].strip()
143
+ logger.info("✅ Cerebras: ناشناس‌سازی موفق")
144
+
145
+ # مرحله 2: استخراج mapping - فقط برای موجودیت‌های انتخابی
146
+ mapping_instructions = []
147
+ json_example = "{\n"
148
+
149
+ if "person" in self.entities_to_anonymize:
150
+ mapping_instructions.append('- برای person-XX: نام کامل شخص (مثلاً "علی احمدی")')
151
+ json_example += ' "person-01": "متن اصلی کامل",\n'
152
+
153
+ if "company" in self.entities_to_anonymize:
154
+ mapping_instructions.append('- برای company-XX: نام کامل شرکت/سازمان (مثلاً "شرکت پتروشیمی")')
155
+ json_example += ' "company-01": "متن اصلی کامل",\n'
156
+
157
+ if "amount" in self.entities_to_anonymize:
158
+ mapping_instructions.append('- برای amount-XX: عدد + واحد (مثلاً "80 هزار تومان" یا "50 میلیارد ریال")')
159
+ json_example += ' "amount-01": "متن اصلی کامل با واحد",\n'
160
+
161
+ if "percent" in self.entities_to_anonymize:
162
+ mapping_instructions.append('- برای percent-XX: عدد + کلمه "درصد" (مثلاً "40 درصد" نه فقط "40")')
163
+ json_example += ' "percent-01": "عدد + درصد",\n'
164
+
165
+ json_example += " ...\n}"
166
+ mapping_instructions_text = "\n".join(mapping_instructions)
167
+
168
+ prompt2 = f"""متن اصلی:
169
+ {text}
170
+
171
+ متن ناشناس شده:
172
+ {anonymized_text}
173
+
174
+ لطفاً یک جدول mapping برای همه توکن‌های ناشناس ایجاد کن.
175
+ برای هر توکن، متن اصلی کامل آن را مشخص کن.
176
+
177
+ **مهم:**
178
+ {mapping_instructions_text}
179
+
180
+ خروجی را به این فرمت JSON بده (فقط JSON، بدون توضیح اضافی):
181
+ {json_example}"""
182
+
183
+ response2 = requests.post(
184
+ "https://api.cerebras.ai/v1/chat/completions",
185
+ headers={
186
+ "Authorization": f"Bearer {self.cerebras_key}",
187
+ "Content-Type": "application/json"
188
+ },
189
+ json={
190
+ "model": "llama-3.3-70b",
191
+ "messages": [{"role": "user", "content": prompt2}],
192
+ "max_tokens": 2048,
193
+ "temperature": 0.1
194
+ },
195
+ timeout=60
196
+ )
197
+
198
+ if response2.status_code == 200:
199
+ mapping_text = response2.json()['choices'][0]['message']['content'].strip()
200
+ mapping_text = mapping_text.replace('```json', '').replace('```', '').strip()
201
+
202
+ try:
203
+ self.mapping_table = json.loads(mapping_text)
204
+ self._fix_percent_mapping()
205
+ self.reverse_mapping = {v: k for k, v in self.mapping_table.items()}
206
+ logger.info(f"✅ Mapping استخراج شد: {len(self.mapping_table)} موجودیت")
207
+ except json.JSONDecodeError:
208
+ logger.warning("⚠️ خطا در parse کردن JSON mapping - استفاده از روش fallback")
209
+ self._extract_mapping_from_text(text, anonymized_text)
210
+ else:
211
+ logger.warning("⚠️ خطا در دریافت mapping - استفاده از روش fallback")
212
+ self._extract_mapping_from_text(text, anonymized_text)
213
+
214
+ return anonymized_text, self.mapping_table
215
+
216
+ except Exception as e:
217
+ logger.error(f"❌ Cerebras Exception: {e}")
218
+ raise
219
+
220
+ def _fix_percent_mapping(self):
221
+ """اصلاح mapping برای درصدها"""
222
+ for token, value in self.mapping_table.items():
223
+ value_str = str(value).strip()
224
+
225
+ if token.startswith('percent-'):
226
+ if not re.search(r'(درصد|%|درصدی)', value_str):
227
+ self.mapping_table[token] = f"{value_str} درصد"
228
+ logger.info(f"✅ اصلاح {token}: '{value_str}' → '{value_str} درصد'")
229
+
230
+ elif token.startswith('amount-'):
231
+ if not re.search(r'(میلیارد|میلیون|هزار|تومان|ریال|دلار|یورو|تن)', value_str):
232
+ logger.warning(f"⚠️ {token}: فقط عدد '{value_str}' - واحد مشخص نیست")
233
+
234
+ def _extract_mapping_from_text(self, original: str, anonymized: str):
235
+ """استخراج mapping از متن‌های اصلی و ناشناس شده - فقط برای موجودیت‌های انتخابی"""
236
+
237
+ # ✅ استخراج فقط توکن‌های انتخابی
238
+ all_tokens = []
239
+ for entity_type in self.entities_to_anonymize:
240
+ tokens = re.findall(f'{entity_type}-\\d+', anonymized)
241
+ all_tokens.extend([(t, entity_type) for t in tokens])
242
+
243
+ all_tokens = sorted(set(all_tokens), key=lambda x: (x[1], int(x[0].split('-')[1])))
244
+
245
+ # ✅ الگوهای موجودیت - فقط برای انتخابی‌ها
246
+ patterns = {}
247
+ if "person" in self.entities_to_anonymize:
248
+ patterns['person'] = r'\b[ء-ي]+\s+[ء-ي]+(?:\s+[ء-ي]+)*\b'
249
+ if "company" in self.entities_to_anonymize:
250
+ patterns['company'] = r'(?:شرکت|بانک|سازمان|گروه|هلدینگ)\s+[ء-ي]+(?:\s+[ء-ي]+)*'
251
+ if "amount" in self.entities_to_anonymize:
252
+ patterns['amount'] = r'\d+(?:\.\d+)?\s*(?:میلیارد|میلیون|هزار|تومان|ریال|دلار|یورو|تن)'
253
+ if "percent" in self.entities_to_anonymize:
254
+ patterns['percent'] = r'\d+(?:\.\d+)?\s*(?:درصد|%|درصدی)'
255
+
256
+ original_entities = {}
257
+ for entity_type, pattern in patterns.items():
258
+ matches = list(re.finditer(pattern, original))
259
+ original_entities[entity_type] = [m.group().strip() for m in matches]
260
+
261
+ for token, entity_type in all_tokens:
262
+ if entity_type in original_entities and original_entities[entity_type]:
263
+ token_num = int(token.split('-')[1]) - 1
264
+
265
+ if token_num < len(original_entities[entity_type]):
266
+ original_text = original_entities[entity_type][token_num]
267
+ self.mapping_table[token] = original_text
268
+ self.reverse_mapping[original_text] = token
269
+ else:
270
+ original_text = original_entities[entity_type][-1]
271
+ if token not in self.mapping_table:
272
+ self.mapping_table[token] = original_text
273
+ self.reverse_mapping[original_text] = token
274
+
275
+ def analyze_with_llm(self, anonymized_text: str, analysis_prompt: str = None) -> str:
276
+ """استفاده از LLM یکپارچه"""
277
+ logger.info(f"🤖 {self.llm_provider.upper()} اجرای پرامپت...")
278
+
279
+ if not analysis_prompt or not analysis_prompt.strip():
280
+ logger.info("⚠️ پرامپت خالی - بدون تحلیل")
281
+ return "⚠️ هیچ دستور تحلیل داده نشده است"
282
+
283
+ # ✅ ساخت پیام توجه بر اساس موجودیت‌های انتخاب‌شده
284
+ tokens_instruction = []
285
+ examples = []
286
+
287
+ if "person" in self.entities_to_anonymize:
288
+ tokens_instruction.append("person-XX")
289
+ examples.append("✅ صحیح: person-01 در جلسه حضور داشت\n❌ غلط: آقای person-01 یا شخص person-01")
290
+
291
+ if "company" in self.entities_to_anonymize:
292
+ tokens_instruction.append("company-XX")
293
+ examples.append("✅ صحیح: company-01 فعالیت کرد\n❌ غلط: شرکت company-01 یا سازمان company-01")
294
+
295
+ if "amount" in self.entities_to_anonymize:
296
+ tokens_instruction.append("amount-XX")
297
+ examples.append("✅ صحیح: درآمد amount-01 میلیون ریال\n❌ غلط: مبلغ amount-01 یا رقم amount-01")
298
+
299
+ if "percent" in self.entities_to_anonymize:
300
+ tokens_instruction.append("percent-XX")
301
+ examples.append("✅ صحیح: رشد percent-01 داشت\n❌ غلط: درصد percent-01")
302
+
303
+ tokens_str = ", ".join(tokens_instruction)
304
+ examples_str = "\n".join(examples)
305
+
306
+ combined_text = f"""متن ناشناس‌سازی شده:
307
+ {anonymized_text}
308
+
309
+ دستورات:
310
+ {analysis_prompt}
311
+
312
+ ⚠️ قوانین مهم:
313
+ 1. فقط از کدهای ناشناس موجود استفاده کن: {tokens_str}
314
+ 2. هیچ کلمه‌ای قبل یا بعد از این کدها اضافه نکن
315
+ 3. کد جدید ایجاد نکن
316
+ 4. ساختار دقیق متن را حفظ کن
317
+
318
+ مثال‌های صحیح و غلط:
319
+ {examples_str}
320
+
321
+ هشدار: اگر کلمه‌ای مثل "شرکت"، "آقای"، "مبلغ" قبل از کدها بگذاری، پاسخ غلط است!"""
322
+
323
+ try:
324
+ response = self.llm_sender.send_simple(combined_text, lang='fa')
325
+
326
+ # ✅ پاکسازی کلمات اضافی (لایه امنیتی دوم)
327
+ response = self._clean_llm_response(response)
328
+
329
+ logger.info(f"✅ {self.llm_provider.upper()}: {len(response)} کاراکتر")
330
+ return response
331
+ except Exception as e:
332
+ logger.error(f"❌ {self.llm_provider.upper()} Exception: {e}")
333
+ return f"❌ خطا در ارتباط با {self.llm_provider.upper()}: {str(e)}"
334
+
335
+ def _clean_llm_response(self, text: str) -> str:
336
+ """پاکسازی کلمات اضافی که LLM ممکن است قبل از موجودیت‌ها اضافه کرده باشد"""
337
+ logger.info("🧹 پاکسازی کلمات اضافی...")
338
+
339
+ cleaned = text
340
+ changes_made = 0
341
+
342
+ # الگوهای کلمات اضافی برای هر نوع موجودیت
343
+ patterns = []
344
+
345
+ if "person" in self.entities_to_anonymize:
346
+ patterns.extend([
347
+ (r'(?:آقای|خانم|شخص)\s+(person-\d+)', r'\1'),
348
+ (r'(person-\d+)\s+(?:نامدار|محترم)', r'\1'),
349
+ ])
350
+
351
+ if "company" in self.entities_to_anonymize:
352
+ patterns.extend([
353
+ (r'(?:شرکت|سازمان|گروه|هلدینگ|بانک)\s+(company-\d+)', r'\1'),
354
+ (r'(company-\d+)\s+(?:محترم)', r'\1'),
355
+ ])
356
+
357
+ if "amount" in self.entities_to_anonymize:
358
+ patterns.extend([
359
+ (r'(?:مبلغ|رقم|عدد)\s+(amount-\d+)', r'\1'),
360
+ ])
361
+
362
+ if "percent" in self.entities_to_anonymize:
363
+ patterns.extend([
364
+ (r'(?:درصد|%)\s+(percent-\d+)', r'\1'),
365
+ ])
366
+
367
+ # اعمال الگوها
368
+ for pattern, replacement in patterns:
369
+ new_text = re.sub(pattern, replacement, cleaned)
370
+ if new_text != cleaned:
371
+ changes_made += re.subn(pattern, replacement, cleaned)[1]
372
+ cleaned = new_text
373
+
374
+ if changes_made > 0:
375
+ logger.info(f"✅ {changes_made} کلمه اضافی حذف شد")
376
+ else:
377
+ logger.info("✅ کلمه اضافی یافت نشد")
378
+
379
+ return cleaned
380
+
381
+ def restore_text(self, anonymized_text: str) -> str:
382
+ """بازگردانی متن"""
383
+ logger.info("🔄 بازگردانی متن...")
384
+
385
+ if not self.mapping_table:
386
+ logger.warning("⚠️ جدول نگاشت خالی است")
387
+ return anonymized_text
388
+
389
+ restored = anonymized_text
390
+ for placeholder, original in sorted(self.mapping_table.items()):
391
+ restored = restored.replace(placeholder, original)
392
+
393
+ logger.info("✅ بازگردانی کامل")
394
+ return restored
395
+
396
+ def get_mapping_table_md(self) -> str:
397
+ """تبدیل جدول نگاشت به Markdown"""
398
+ if not self.mapping_table:
399
+ return "### 📋 جدول نگاشت\n\nهیچ موجودیتی شناسایی نشد"
400
+
401
+ table = "### 📋 جدول نگاشت\n\n"
402
+ table += "| شناسه | متن اصلی |\n"
403
+ table += "|-------|----------|\n"
404
+
405
+ for token, original in sorted(self.mapping_table.items()):
406
+ table += f"| **{token}** | {original} |\n"
407
+
408
+ return table
409
+
410
+ # متغیر سراسری
411
+ anonymizer = None
412
+
413
+ def process(
414
+ input_text: str,
415
+ analysis_prompt: str,
416
+ llm_provider: str,
417
+ llm_model: str,
418
+ api_key_input: str,
419
+ anonymize_all: bool,
420
+ anonymize_person: bool,
421
+ anonymize_company: bool,
422
+ anonymize_amount: bool,
423
+ anonymize_percent: bool
424
+ ):
425
+ """پردازش متن - 4 مرحله"""
426
+ global anonymizer
427
+
428
+ if not input_text.strip():
429
+ return "", "", "", ""
430
+
431
+ # ✅ ساخت لیست موجودیت‌های انتخابی
432
+ if anonymize_all:
433
+ entities = ["person", "company", "amount", "percent"]
434
+ else:
435
+ entities = []
436
+ if anonymize_person:
437
+ entities.append("person")
438
+ if anonymize_company:
439
+ entities.append("company")
440
+ if anonymize_amount:
441
+ entities.append("amount")
442
+ if anonymize_percent:
443
+ entities.append("percent")
444
+
445
+ # اگه هیچی انتخاب نشده
446
+ if not entities:
447
+ return "", "❌ لطفاً حداقل یک موجودیت برای ناشناس‌سازی انتخاب کنید", "", ""
448
+
449
+ cerebras_key = os.getenv("CEREBRAS_API_KEY")
450
+
451
+ # ایجاد یا آپدیت anonymizer
452
+ if not anonymizer:
453
+ anonymizer = AnonymizerAdvanced(
454
+ cerebras_key,
455
+ llm_provider=llm_provider,
456
+ llm_model=llm_model,
457
+ llm_api_key=api_key_input,
458
+ entities_to_anonymize=entities
459
+ )
460
+ else:
461
+ anonymizer.set_llm_provider(llm_provider, llm_model, api_key_input, entities)
462
+ anonymizer.mapping_table = {}
463
+ anonymizer.reverse_mapping = {}
464
+
465
+ try:
466
+ logger.info("=" * 70)
467
+ logger.info(f"🚀 شروع پردازش - LLM: {llm_provider} ({llm_model})")
468
+ logger.info(f"🎯 موجودیت‌های انتخابی: {', '.join(entities)}")
469
+ logger.info("=" * 70)
470
+
471
+ # مرحله 1: ناشناس‌سازی
472
+ logger.info("🔐 مرحله 1: ناشناس‌سازی...")
473
+ anonymized_text, _ = anonymizer.anonymize_with_cerebras(input_text)
474
+ logger.info(f"✅ ناشناس‌سازی: {len(anonymized_text)} کاراکتر")
475
+
476
+ # مرحله 2: LLM
477
+ logger.info(f"🤖 مرحله 2: {llm_provider.upper()}...")
478
+ llm_response = anonymizer.analyze_with_llm(anonymized_text, analysis_prompt)
479
+ logger.info(f"✅ {llm_provider.upper()}: {len(llm_response)} کاراکتر")
480
+
481
+ # مرحله 3: بازگردانی
482
+ logger.info("🔄 مرحله 3: بازگردانی...")
483
+ restored_text = anonymizer.restore_text(llm_response)
484
+ logger.info("✅ بازگردانی کامل")
485
+
486
+ # مرحله 4: جدول نگاشت
487
+ logger.info("📋 مرحله 4: جدول نگاشت...")
488
+ mapping_str = anonymizer.get_mapping_table_md()
489
+ logger.info(f"✅ {len(anonymizer.mapping_table)} موجودیت")
490
+
491
+ logger.info("=" * 70)
492
+ logger.info("✅ تمام مراحل کامل!")
493
+ logger.info("=" * 70)
494
+
495
+ return restored_text, llm_response, anonymized_text, mapping_str
496
+
497
+ except Exception as e:
498
+ logger.error(f"❌ خطا: {str(e)}", exc_info=True)
499
+ return "", f"❌ خطا: {str(e)}", "", ""
500
+
501
+ def clear_all():
502
+ """پاک کردن همه"""
503
+ return "", "", "", "", "", "", "", True, False, False, False, False
504
+
505
+ # Gradio Interface
506
+ css_rtl = """
507
+ .input-box {
508
+ direction: rtl;
509
+ text-align: right;
510
+ }
511
+ .textbox textarea {
512
+ direction: rtl;
513
+ text-align: right;
514
+ font-family: 'Tahoma', serif;
515
+ }
516
+ .thick-divider {
517
+ border-top: 3px solid #333;
518
+ margin: 30px 0;
519
+ }
520
+ """
521
+
522
+ with gr.Blocks(title="سیستم ناشناس‌سازی متون", theme=gr.themes.Soft(), css=css_rtl) as app:
523
+
524
+ gr.Markdown("# 🔐 پلتفرم امن چت با مدل‌های متنوع و ناشناس‌سازی داده‌ها", elem_classes="input-box")
525
+
526
+ # ردیف اول: تنظیمات مدل و انتخاب موجودیت‌ها
527
+ with gr.Row():
528
+ # سمت راست: تنظیمات مدل
529
+ with gr.Column(scale=1):
530
+ with gr.Group():
531
+ gr.Markdown("### ⚙️ تنظیمات مدل", elem_classes="input-box")
532
+
533
+ llm_provider = gr.Dropdown(
534
+ choices=["chatgpt", "grok"],
535
+ value="chatgpt",
536
+ label="🤖 انتخاب مدل زبانی",
537
+ interactive=True
538
+ )
539
+
540
+ llm_model = gr.Dropdown(
541
+ choices=AVAILABLE_MODELS["chatgpt"],
542
+ value="gpt-4o-mini",
543
+ label="📦 انتخاب نسخه مدل",
544
+ interactive=True
545
+ )
546
+
547
+ api_key_input = gr.Textbox(
548
+ label="🔑 API Key",
549
+ placeholder="فقط برای مدل‌های غیر از gpt-4o-mini",
550
+ type="password",
551
+ visible=False,
552
+ elem_classes="textbox"
553
+ )
554
+
555
+ # سمت چپ: انتخاب موجودیت‌ها
556
+ with gr.Column(scale=1):
557
+ with gr.Group():
558
+ gr.Markdown("### 🎯 انتخاب موجودیت‌ها", elem_classes="input-box")
559
+
560
+ anonymize_all = gr.Checkbox(
561
+ label="✅ همه موجودیت‌ها",
562
+ value=True,
563
+ elem_classes="input-box"
564
+ )
565
+
566
+ anonymize_person = gr.Checkbox(
567
+ label="👤 اسامی اشخاص",
568
+ value=False,
569
+ elem_classes="input-box"
570
+ )
571
+
572
+ anonymize_company = gr.Checkbox(
573
+ label="🏢 نام شرکت‌ها",
574
+ value=False,
575
+ elem_classes="input-box"
576
+ )
577
+
578
+ anonymize_amount = gr.Checkbox(
579
+ label="💰 ارقام مالی",
580
+ value=False,
581
+ elem_classes="input-box"
582
+ )
583
+
584
+ anonymize_percent = gr.Checkbox(
585
+ label="📊 درصدها",
586
+ value=False,
587
+ elem_classes="input-box"
588
+ )
589
+
590
+ # خط جداکننده پررنگ
591
+ gr.Markdown("---", elem_classes="thick-divider")
592
+
593
+ # ردیف دوم: دستورات پردازش و متن ورودی
594
+ with gr.Row():
595
+ # سمت راست: دستورات پردازش
596
+ with gr.Column(scale=1):
597
+ gr.Markdown("### 📋 دستورات پردازش", elem_classes="input-box")
598
+
599
+ analysis_prompt = gr.Textbox(
600
+ lines=22,
601
+ placeholder="مثال: این متن را خلاصه کن\nیا: نکات کلیدی را استخراج کن",
602
+ label="📋 دستورات LLM (اختیاری)",
603
+ elem_classes="textbox"
604
+ )
605
+
606
+ # سمت چپ: متن ورودی
607
+ with gr.Column(scale=1):
608
+ gr.Markdown("### 📝 متن ورودی", elem_classes="input-box")
609
+
610
+ input_text = gr.Textbox(
611
+ lines=22,
612
+ placeholder="متن مالی/خبری را وارد کنید...",
613
+ label="",
614
+ elem_classes="textbox"
615
+ )
616
+
617
+ # دکمه‌های پردازش و پاک کردن
618
+ with gr.Row():
619
+ process_btn = gr.Button(
620
+ "▶️ پردازش",
621
+ variant="primary",
622
+ size="lg",
623
+ scale=2
624
+ )
625
+
626
+ clear_btn = gr.Button(
627
+ "🗑️ پاک کردن",
628
+ variant="stop",
629
+ size="lg",
630
+ scale=1
631
+ )
632
+
633
+ # نتایج
634
+ gr.Markdown("## 📊 نتایج پردازش", elem_classes="input-box")
635
+
636
+ with gr.Row():
637
+ with gr.Column(scale=1):
638
+ restored_text = gr.Textbox(
639
+ lines=12,
640
+ label="✅ متن بازگردانی شده",
641
+ interactive=False,
642
+ elem_classes="textbox"
643
+ )
644
+
645
+ with gr.Column(scale=1):
646
+ llm_analysis = gr.Textbox(
647
+ lines=12,
648
+ label="🤖 تحلیل LLM",
649
+ interactive=False,
650
+ elem_classes="textbox"
651
+ )
652
+
653
+ with gr.Column(scale=1):
654
+ anonymized_text = gr.Textbox(
655
+ lines=12,
656
+ label="🔒 متن ناشناس‌شده",
657
+ interactive=False,
658
+ elem_classes="textbox"
659
+ )
660
+
661
+ mapping_table = gr.Markdown(
662
+ value="### 📋 جدول نگاشت\n\nهنوز پردازشی انجام نشده",
663
+ label="📋 جدول نگاشت",
664
+ elem_classes="input-box"
665
+ )
666
+
667
+ # Event Handlers
668
+ def handle_provider_change(provider):
669
+ models = AVAILABLE_MODELS.get(provider, [])
670
+ default_model = models[0] if models else None
671
+ show_api = not (provider == "chatgpt" and default_model == "gpt-4o-mini")
672
+
673
+ return (
674
+ gr.Dropdown(choices=models, value=default_model),
675
+ gr.Textbox(visible=show_api, value="")
676
+ )
677
+
678
+ llm_provider.change(
679
+ fn=handle_provider_change,
680
+ inputs=[llm_provider],
681
+ outputs=[llm_model, api_key_input]
682
+ )
683
+
684
+ def handle_model_change(provider, model):
685
+ show_api = not (provider == "chatgpt" and model == "gpt-4o-mini")
686
+ return gr.Textbox(visible=show_api, value="")
687
+
688
+ llm_model.change(
689
+ fn=handle_model_change,
690
+ inputs=[llm_provider, llm_model],
691
+ outputs=[api_key_input]
692
+ )
693
+
694
+ def handle_select_all(select_all):
695
+ if select_all:
696
+ return (
697
+ gr.Checkbox(value=False, interactive=False),
698
+ gr.Checkbox(value=False, interactive=False),
699
+ gr.Checkbox(value=False, interactive=False),
700
+ gr.Checkbox(value=False, interactive=False)
701
+ )
702
+ else:
703
+ return (
704
+ gr.Checkbox(value=False, interactive=True),
705
+ gr.Checkbox(value=False, interactive=True),
706
+ gr.Checkbox(value=False, interactive=True),
707
+ gr.Checkbox(value=False, interactive=True)
708
+ )
709
+
710
+ anonymize_all.change(
711
+ fn=handle_select_all,
712
+ inputs=[anonymize_all],
713
+ outputs=[anonymize_person, anonymize_company, anonymize_amount, anonymize_percent]
714
+ )
715
+
716
+ # پردازش
717
+ process_btn.click(
718
+ fn=process,
719
+ inputs=[
720
+ input_text,
721
+ analysis_prompt,
722
+ llm_provider,
723
+ llm_model,
724
+ api_key_input,
725
+ anonymize_all,
726
+ anonymize_person,
727
+ anonymize_company,
728
+ anonymize_amount,
729
+ anonymize_percent
730
+ ],
731
+ outputs=[restored_text, llm_analysis, anonymized_text, mapping_table]
732
+ )
733
+
734
+ # پاک کردن
735
+ clear_btn.click(
736
+ fn=clear_all,
737
+ outputs=[
738
+ input_text,
739
+ analysis_prompt,
740
+ api_key_input,
741
+ restored_text,
742
+ llm_analysis,
743
+ anonymized_text,
744
+ mapping_table,
745
+ anonymize_all,
746
+ anonymize_person,
747
+ anonymize_company,
748
+ anonymize_amount,
749
+ anonymize_percent
750
+ ]
751
+ )
752
+
753
+ if __name__ == "__main__":
754
+ print("=" * 70)
755
+ print("🚀 سیستم ناشناس‌سازی متون در حال راه‌اندازی...")
756
+ print("=" * 70)
757
+ print("\n📋 نحوه استفاده:\n")
758
+ print("1. کلیدهای API را تنظیم کنید:")
759
+ print(" - CEREBRAS_API_KEY (ضروری)")
760
+ print(" - OPENAI_API_KEY (فقط برای gpt-4o-mini)")
761
+ print("2. http://localhost:7860 را باز کنید")
762
+ print("3. LLM و مدل را انتخاب کنید")
763
+ print("4. موجودیت‌های مورد نظر برای ناشناس‌سازی را انتخاب کنید")
764
+ print("5. برای مدل‌های غیر از gpt-4o-mini، API key وارد کنید")
765
+ print("6. متن را وارد کنید")
766
+ print("7. 'پردازش' را کلیک کنید\n")
767
+ print("💡 فقط gpt-4o-mini از Secret می‌خواند")
768
+ print(" بقیه مدل‌ها نیاز به API key دارند")
769
+ print(" اکنون می‌توانید موجودیت‌های خاص را انتخاب کنید")
770
+ print("=" * 70 + "\n")
771
+
772
+ app.launch(
773
+ server_name="0.0.0.0",
774
+ server_port=7860,
775
+ share=False,
776
+ show_error=True
777
+ )