KashefTech commited on
Commit
7c96a11
·
verified ·
1 Parent(s): 20f45da

Upload app_FIXED.py

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