leilaghomashchi commited on
Commit
9a971e8
·
verified ·
1 Parent(s): b2c115d

Upload text_anonymizer_fixed.py

Browse files
Files changed (1) hide show
  1. text_anonymizer_fixed.py +462 -0
text_anonymizer_fixed.py ADDED
@@ -0,0 +1,462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import re
4
+ from collections import defaultdict
5
+ import io
6
+
7
+ class TextAnonymizer:
8
+ def __init__(self):
9
+ self.person_counter = 0
10
+ self.company_counter = 0
11
+ self.amount_counter = 0
12
+ self.percent_counter = 0
13
+
14
+ # دیکشنری برای نگه‌داری تبدیل‌ها
15
+ self.person_mapping = {}
16
+ self.company_mapping = {}
17
+ self.amount_mapping = {}
18
+ self.percent_mapping = {}
19
+
20
+ def reset_counters(self):
21
+ """بازنشانی شمارنده‌ها برای پردازش جدید"""
22
+ self.person_counter = 0
23
+ self.company_counter = 0
24
+ self.amount_counter = 0
25
+ self.percent_counter = 0
26
+ self.person_mapping.clear()
27
+ self.company_mapping.clear()
28
+ self.amount_mapping.clear()
29
+ self.percent_mapping.clear()
30
+
31
+ def detect_financial_amounts(self, text):
32
+ """تشخیص مبالغ مالی (فارسی و انگلیسی)"""
33
+ patterns = [
34
+ # الگوهای انگلیسی
35
+ r'\$[\d,]+(?:\.\d{2})?', # $1,000.00
36
+ r'[\d,]+\s*(?:dollars?|USD|usd|Dollars?)', # 1000 dollars
37
+ r'[\d,]+\s*(?:million|billion|thousand|Million|Billion|Thousand)', # 1 million
38
+ r'[\d,]+(?:\.\d+)?\s*(?:M|B|K|m|b|k)', # 1.5M, 2B, 500K
39
+ r'€[\d,]+(?:\.\d{2})?', # €1,000.00
40
+ r'£[\d,]+(?:\.\d{2})?', # £1,000.00
41
+
42
+ # الگوهای فارسی - ارقام فارسی
43
+ r'[\u06F0-\u06F9\u06F0-\u06F9\d,\u060C]+\s*(?:هزار|میلیون|میلیارد|تریلیون)\s*(?:و\s*[\u06F0-\u06F9\u06F0-\u06F9\d,\u060C]+\s*(?:هزار|میلیون|میلیارد)?)?\s*(?:تومان|ریال|دلار|یورو|درهم)',
44
+ r'[\u06F0-\u06F9\u06F0-\u06F9\d,\u060C]+\s*(?:همت)', # ۳۷ همت
45
+ r'[\u06F0-\u06F9\u06F0-\u06F9\d,\u060C]+\s*(?:هزار|میلیون|میلیارد)\s*(?:تومان|ریال|دلار)',
46
+
47
+ # الگوهای ترکیبی
48
+ r'[\u06F0-\u06F9\u06F0-\u06F9\d]+[\u06F0-\u06F9\u06F0-\u06F9\d,\u060C]*\s*(?:هزار|میلیون|میلیارد)',
49
+ r'بیش\s*از\s*[\u06F0-\u06F9\u06F0-\u06F9\d,\u060C]+\s*(?:هزار|میلیون|میلیارد|همت)',
50
+ r'حدود\s*[\u06F0-\u06F9\u06F0-\u06F9\d,\u060C]+\s*(?:هزار|میلیون|میلیارد|همت)',
51
+ r'نزدیک\s*به\s*[\u06F0-\u06F9\u06F0-\u06F9\d,\u060C]+\s*(?:هزار|میلیون|میلیارد|همت)',
52
+ ]
53
+
54
+ amounts = []
55
+ for pattern in patterns:
56
+ matches = re.finditer(pattern, text, re.IGNORECASE)
57
+ for match in matches:
58
+ amounts.append((match.start(), match.end(), match.group()))
59
+
60
+ return amounts
61
+
62
+ def detect_percentages(self, text):
63
+ """تشخیص درصدها (فارسی و انگلیسی)"""
64
+ patterns = [
65
+ r'\d+(?:\.\d+)?%', # انگلیسی: 15%, 18.5%
66
+ r'[\u06F0-\u06F9\u06F0-\u06F9]+(?:\.[\u06F0-\u06F9\u06F0-\u06F9]+)?\s*درصد', # فارسی: ۱۵ درصد
67
+ r'[\u06F0-\u06F9\u06F0-\u06F9]+(?:\.[\u06F0-\u06F9\u06F0-\u06F9]+)?%', # ترکیبی: ۱۵%
68
+ ]
69
+ percentages = []
70
+ for pattern in patterns:
71
+ matches = re.finditer(pattern, text)
72
+ for match in matches:
73
+ percentages.append((match.start(), match.end(), match.group()))
74
+
75
+ return percentages
76
+
77
+ def detect_names_regex(self, text):
78
+ """تشخیص اسامی اشخاص (فارسی و انگلیسی)"""
79
+ patterns = [
80
+ # الگوهای انگلیسی
81
+ r'\b[A-Z][a-z]+ [A-Z][a-z]+\b', # John Smith
82
+ r'\b[A-Z][a-z]+ [A-Z]\. [A-Z][a-z]+\b', # John M. Smith
83
+ r'\b[A-Z][a-z]+ [A-Z][a-z]+ [A-Z][a-z]+\b', # John Michael Smith
84
+ r'\bMr\. [A-Z][a-z]+\b', # Mr. Smith
85
+ r'\bMs\. [A-Z][a-z]+\b', # Ms. Johnson
86
+ r'\bDr\. [A-Z][a-z]+\b', # Dr. Brown
87
+
88
+ # الگوهای فارسی - نام‌های کامل
89
+ r'[\u0600-\u06FF]+\s+[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+)?(?:\s+[\u0600-\u06FF]+)?', # محمد ایرانی، جواد زارع‌پور
90
+
91
+ # الگوهای خاص فارسی با عناوین
92
+ r'(?:دکتر|آقای|خانم|مهندس|استاد)\s+[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,3}',
93
+
94
+ # الگوی مدیرعامل و سمت‌ها
95
+ r'[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,3}،\s*مدیرعامل',
96
+ r'[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,3}،\s*(?:مدیر|رئیس|نایب)',
97
+
98
+ # نام‌های بین عبارت‌ها
99
+ r'(?:با\s+(?:حضور|سکانداری)\s+)[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,3}',
100
+ r'(?:امضای\s+(?:مشترک\s+)?(?:«)?)[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,3}(?:»)?',
101
+ ]
102
+
103
+ names = []
104
+ processed_spans = set() # برای جلوگیری از تداخل
105
+
106
+ for pattern in patterns:
107
+ matches = re.finditer(pattern, text)
108
+ for match in matches:
109
+ start, end = match.start(), match.end()
110
+ name = match.group().strip()
111
+
112
+ # پاک کردن عناوین و کلمات اضافی
113
+ name = re.sub(r'^(?:دکتر|آقای|خانم|مهندس|استاد)\s+', '', name)
114
+ name = re.sub(r'،\s*(?:مدیرعامل|مدیر|رئیس|نایب).*', '', name)
115
+ name = re.sub(r'^(?:با\s+(?:حضور|سکانداری)\s+)', '', name)
116
+ name = re.sub(r'^(?:امضای\s+(?:مشترک\s+)?(?:«)?)', '', name)
117
+ name = name.strip('،» ()')
118
+
119
+ # بررسی طول نام و عدم تداخل
120
+ if (len(name.split()) >= 2 and
121
+ len(name) > 3 and
122
+ not any(start < existing_end and end > existing_start
123
+ for existing_start, existing_end in processed_spans)):
124
+
125
+ names.append((start, end, name))
126
+ processed_spans.add((start, end))
127
+
128
+ return names
129
+
130
+ def detect_companies_regex(self, text):
131
+ """تشخیص شرکت‌ها (فارسی و انگلیسی)"""
132
+ # الگوهای عمومی انگلیسی
133
+ general_patterns = [
134
+ r'\b[A-Z][a-z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation|Group|Technologies|Tech|Systems|Solutions|Services|International|Global|Enterprises)\.?\b',
135
+ r'\b[A-Z][A-Z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation)\.?\b', # مثل IBM Corp
136
+ r'\b[A-Z][a-z]+ [A-Z][a-z]+ (?:Inc|Corp|LLC|Ltd|Company|Co|Corporation)\.?\b', # مثل Apple Inc
137
+ ]
138
+
139
+ # شرکت‌های مشهور انگلیسی
140
+ tech_companies = r'\b(?:Apple|Microsoft|Google|Amazon|Facebook|Meta|Netflix|Tesla|Oracle|IBM|Intel|Cisco|Adobe|Salesforce|PayPal|Uber|Airbnb|Twitter|LinkedIn|NVIDIA|AMD|Zoom|Slack|Dropbox|Spotify)\b'
141
+ auto_companies = r'\b(?:Toyota|Honda|Ford|BMW|Mercedes|Audi|Volkswagen|Nissan|Hyundai|Kia|Mazda|Subaru|Volvo|Porsche|Ferrari|Lamborghini)\b'
142
+ finance_companies = r'\b(?:JPMorgan|Goldman Sachs|Morgan Stanley|Bank of America|Wells Fargo|Chase|Citibank|American Express|Visa|Mastercard|PayPal)\b'
143
+ retail_companies = r'\b(?:Walmart|Target|Costco|Amazon|eBay|Alibaba|Nike|Adidas|Zara|H&M|IKEA|Starbucks|McDonalds|KFC|Subway)\b'
144
+
145
+ # الگوهای فارسی - شرکت‌ها و سازمان‌ها
146
+ persian_company_patterns = [
147
+ # الگوی کلی شرکت
148
+ r'شرکت\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
149
+ r'گروه\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
150
+ r'هلدینگ\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
151
+ r'بانک\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
152
+ r'بیمه\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
153
+ r'پتروشیمی\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
154
+ r'صنایع\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
155
+ r'فولاد\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
156
+ r'سازمان\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
157
+ r'موسسه\s+[\u0600-\u06FF\s]+(?:[\u0600-\u06FF])',
158
+
159
+ # شرکت‌های خاص از نمونه‌ها
160
+ r'ایران\s*خودرو',
161
+ r'همراه\s*اول',
162
+ r'فولاد\s*مبارکه(?:\s+اصفهان)?',
163
+ r'بانک\s+(?:ملت|پاسارگاد|سرمایه|مرکزی|کشاورزی)',
164
+ r'بیمه\s+(?:پارسیان|سامان)',
165
+ r'پتروشیمی\s+(?:پارس|بوعلی\s*سینا|اروند)',
166
+
167
+ # الگوی با مخفف
168
+ r'[\u0600-\u06FF\s]+\s*\([\u0600-\u06FF\s]+\)', # مثل تأمین (تیپیکو)
169
+
170
+ # الگوی نام‌های مرکب
171
+ r'[\u0600-\u06FF]+(?:\s+[\u0600-\u06FF]+){1,4}(?:\s+(?:شرکت|گروه|بانک|بیمه|صنایع))?',
172
+ ]
173
+
174
+ # ترکیب تمام الگوها
175
+ all_patterns = general_patterns + [
176
+ tech_companies,
177
+ auto_companies,
178
+ finance_companies,
179
+ retail_companies
180
+ ] + persian_company_patterns
181
+
182
+ companies = []
183
+ processed_spans = set() # برای جلوگیری از تداخل
184
+
185
+ for pattern in all_patterns:
186
+ matches = re.finditer(pattern, text, re.IGNORECASE)
187
+ for match in matches:
188
+ start, end = match.start(), match.end()
189
+ company = match.group().strip()
190
+
191
+ # فیلتر کردن نتایج خیلی کوتاه یا طولانی
192
+ if (len(company) > 2 and len(company) < 100 and
193
+ not any(start < existing_end and end > existing_start
194
+ for existing_start, existing_end in processed_spans)):
195
+
196
+ companies.append((start, end, company))
197
+ processed_spans.add((start, end))
198
+
199
+ return companies
200
+
201
+ def anonymize_text(self, text):
202
+ """ناشناس‌سازی متن با regex"""
203
+ if not text or pd.isna(text):
204
+ return text
205
+
206
+ replacements = []
207
+
208
+ # تشخیص اسامی اشخاص
209
+ names = self.detect_names_regex(text)
210
+ for start, end, name in names:
211
+ if name not in self.person_mapping:
212
+ self.person_counter += 1
213
+ self.person_mapping[name] = f"person-{self.person_counter:02d}"
214
+ replacements.append((start, end, self.person_mapping[name]))
215
+
216
+ # تشخیص شرکت‌ها
217
+ companies = self.detect_companies_regex(text)
218
+ for start, end, company in companies:
219
+ if company not in self.company_mapping:
220
+ self.company_counter += 1
221
+ self.company_mapping[company] = f"company-{self.company_counter:02d}"
222
+ replacements.append((start, end, self.company_mapping[company]))
223
+
224
+ # تشخیص مبالغ مالی
225
+ amounts = self.detect_financial_amounts(text)
226
+ for start, end, amount in amounts:
227
+ if amount not in self.amount_mapping:
228
+ self.amount_counter += 1
229
+ self.amount_mapping[amount] = f"amount-{self.amount_counter:02d}"
230
+ replacements.append((start, end, self.amount_mapping[amount]))
231
+
232
+ # تشخیص درصدها
233
+ percentages = self.detect_percentages(text)
234
+ for start, end, percent in percentages:
235
+ if percent not in self.percent_mapping:
236
+ self.percent_counter += 1
237
+ self.percent_mapping[percent] = f"percent-{self.percent_counter:02d}"
238
+ replacements.append((start, end, self.percent_mapping[percent]))
239
+
240
+ # حذف تداخل‌ها و مرتب‌سازی
241
+ replacements = self.remove_overlaps(replacements)
242
+ replacements.sort(key=lambda x: x[0], reverse=True)
243
+
244
+ # اعمال جایگزینی‌ها
245
+ result = text
246
+ for start, end, replacement in replacements:
247
+ result = result[:start] + replacement + result[end:]
248
+
249
+ return result
250
+
251
+ def remove_overlaps(self, replacements):
252
+ """حذف تداخل‌ها در جایگزینی‌ها"""
253
+ if not replacements:
254
+ return []
255
+
256
+ # مرتب‌سازی بر اساس موقعیت شروع
257
+ replacements.sort(key=lambda x: x[0])
258
+
259
+ filtered = []
260
+ for start, end, replacement in replacements:
261
+ # بررسی تداخل با آخرین جایگزینی اضافه شده
262
+ if not filtered or start >= filtered[-1][1]:
263
+ filtered.append((start, end, replacement))
264
+
265
+ return filtered
266
+
267
+ def get_mapping_summary(self):
268
+ """خلاصه‌ای از تبدیل‌های انجام شده"""
269
+ summary = []
270
+
271
+ if self.person_mapping:
272
+ summary.append("**اسامی اشخاص:**")
273
+ for original, anonymized in self.person_mapping.items():
274
+ summary.append(f"- {original} → {anonymized}")
275
+ summary.append("")
276
+
277
+ if self.company_mapping:
278
+ summary.append("**نام شرکت‌ها:**")
279
+ for original, anonymized in self.company_mapping.items():
280
+ summary.append(f"- {original} → {anonymized}")
281
+ summary.append("")
282
+
283
+ if self.amount_mapping:
284
+ summary.append("**مبالغ مالی:**")
285
+ for original, anonymized in self.amount_mapping.items():
286
+ summary.append(f"- {original} → {anonymized}")
287
+ summary.append("")
288
+
289
+ if self.percent_mapping:
290
+ summary.append("**درصدها:**")
291
+ for original, anonymized in self.percent_mapping.items():
292
+ summary.append(f"- {original} → {anonymized}")
293
+
294
+ return "\n".join(summary) if summary else "هیچ موجودیت حساسی یافت نشد."
295
+
296
+ # ایجاد نمونه از کلاس ناشناس‌ساز
297
+ anonymizer = TextAnonymizer()
298
+
299
+ def process_csv(file):
300
+ """پردازش فایل CSV"""
301
+ try:
302
+ # بازنشانی شمارنده‌ها
303
+ anonymizer.reset_counters()
304
+
305
+ # بررسی فایل
306
+ if file is None:
307
+ return None, "لطفاً فایل CSV آپلود کنید.", "", None
308
+
309
+ # خواندن فایل CSV
310
+ if file.name.endswith('.csv'):
311
+ df = pd.read_csv(file.name)
312
+ else:
313
+ return None, "لطفاً فایل CSV آپلو�� کنید.", "", None
314
+
315
+ # بررسی وجود ستون original_text
316
+ if 'original_text' not in df.columns:
317
+ available_columns = ', '.join(df.columns.tolist())
318
+ return None, f"ستون 'original_text' در فایل یافت نشد. ستون‌های موجود: {available_columns}", "", None
319
+
320
+ # ایجاد کپی از دیتافریم
321
+ result_df = df.copy()
322
+
323
+ # ناشناس‌سازی متن‌های ستون original_text
324
+ result_df['anonymized_text'] = df['original_text'].apply(anonymizer.anonymize_text)
325
+
326
+ # تبدیل به CSV برای دانلود
327
+ output = io.StringIO()
328
+ result_df.to_csv(output, index=False, encoding='utf-8')
329
+ csv_content = output.getvalue()
330
+
331
+ # ایجاد فایل CSV برای دانلود
332
+ output_file = "anonymized_data.csv"
333
+ with open(output_file, 'w', encoding='utf-8') as f:
334
+ f.write(csv_content)
335
+
336
+ # نمایش نمونه از نتایج
337
+ sample_df = result_df[['original_text', 'anonymized_text']].head(10)
338
+
339
+ # خلاصه تبدیل‌ها
340
+ mapping_summary = anonymizer.get_mapping_summary()
341
+
342
+ return output_file, f"✅ پردازش کامل شد! {len(df)} ردیف پردازش شد.", mapping_summary, sample_df
343
+
344
+ except Exception as e:
345
+ return None, f"❌ خطا در پردازش فایل: {str(e)}", "", None
346
+
347
+ def process_single_text(text):
348
+ """پردازش تک متن"""
349
+ if not text.strip():
350
+ return "", "لطفاً متنی وارد کنید."
351
+
352
+ anonymizer.reset_counters()
353
+ anonymized = anonymizer.anonymize_text(text)
354
+ mapping_summary = anonymizer.get_mapping_summary()
355
+
356
+ return anonymized, mapping_summary
357
+
358
+ # ایجاد رابط کاربری Gradio
359
+ with gr.Blocks(title="ناشناس‌سازی متن", theme=gr.themes.Soft()) as demo:
360
+ gr.Markdown("""
361
+ # 🔒 برنامه ناشناس‌سازی متن (نسخه Regex)
362
+
363
+ ⚡ **وضعیت:** حالت سریع - بدون نیاز به spaCy
364
+
365
+ این برنامه متن‌های شما را ناشناس می‌کند و اطلاعات حساس زیر را جایگزین می‌کند:
366
+ - 👤 **اسامی اشخاص** → person-01, person-02, ...
367
+ - 🏢 **نام شرکت‌ها** → company-01, company-02, ...
368
+ - 💰 **مبالغ مالی** → amount-01, amount-02, ...
369
+ - 📊 **درصدها** → percent-01, percent-02, ...
370
+
371
+ **نسخه ۱:** آدرس‌ها، مکان‌ها و تاریخ‌ها ناشناس‌سازی نمی‌شوند.
372
+ """)
373
+
374
+ with gr.Tabs():
375
+ # تب پردازش فایل CSV
376
+ with gr.TabItem("📁 پردازش فایل CSV"):
377
+ gr.Markdown("### آپلود فایل CSV با ستون 'original_text'")
378
+
379
+ with gr.Row():
380
+ with gr.Column():
381
+ file_input = gr.File(
382
+ label="فایل CSV را انتخاب کنید",
383
+ file_types=[".csv"],
384
+ type="filepath"
385
+ )
386
+ process_btn = gr.Button("🚀 شروع پردازش", variant="primary")
387
+
388
+ with gr.Column():
389
+ status_output = gr.Textbox(
390
+ label="وضعیت",
391
+ interactive=False
392
+ )
393
+ download_file = gr.File(
394
+ label="دانلود فایل ناشناس‌سازی شده",
395
+ interactive=False
396
+ )
397
+
398
+ with gr.Row():
399
+ with gr.Column():
400
+ mapping_output = gr.Markdown(
401
+ label="خلاصه تبدیل‌ها",
402
+ value="خلاصه تبدیل‌ها اینجا نمایش داده می‌شود..."
403
+ )
404
+
405
+ with gr.Column():
406
+ sample_output = gr.Dataframe(
407
+ label="نمونه نتایج (۱۰ ردیف اول)",
408
+ interactive=False
409
+ )
410
+
411
+ # تب تست تک متن
412
+ with gr.TabItem("📝 تست تک متن"):
413
+ gr.Markdown("### تست ناشناس‌سازی روی یک متن")
414
+
415
+ with gr.Row():
416
+ with gr.Column():
417
+ text_input = gr.Textbox(
418
+ label="متن اصلی",
419
+ placeholder="متن خود را اینجا وارد کنید...",
420
+ lines=5
421
+ )
422
+ test_btn = gr.Button("🔍 ناشناس‌سازی", variant="primary")
423
+
424
+ with gr.Column():
425
+ text_output = gr.Textbox(
426
+ label="متن ناشناس‌سازی شده",
427
+ lines=5,
428
+ interactive=False
429
+ )
430
+ text_mapping = gr.Markdown(
431
+ label="تبدیل‌های انجام شده"
432
+ )
433
+
434
+ # اتصال رویدادها
435
+ process_btn.click(
436
+ fn=process_csv,
437
+ inputs=[file_input],
438
+ outputs=[download_file, status_output, mapping_output, sample_output]
439
+ )
440
+
441
+ test_btn.click(
442
+ fn=process_single_text,
443
+ inputs=[text_input],
444
+ outputs=[text_output, text_mapping]
445
+ )
446
+
447
+ # مثال در بخش تست
448
+ gr.Examples(
449
+ examples=[
450
+ ["John Smith works at Microsoft and earned $50,000 with a 15% bonus."],
451
+ ["Sarah Johnson from Google Inc. reported revenues of $2.5 million, representing a 25% increase."],
452
+ ["The CEO of Apple, Tim Cook, announced profits of $1.2B with 18.5% growth rate."],
453
+ ["Dr. Michael Brown from IBM Corp. received €75,000 salary increase of 12%."],
454
+ ["Ms. Lisa Wilson at Amazon reported quarterly results of £500K with 8.7% margin."]
455
+ ],
456
+ inputs=[text_input],
457
+ label="نمونه متن‌ها"
458
+ )
459
+
460
+ # اجرای برنامه
461
+ if __name__ == "__main__":
462
+ demo.launch()