KashefTech commited on
Commit
96406cd
·
verified ·
1 Parent(s): 90436dd

Upload app_updated_models.py

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