leilaghomashchi commited on
Commit
419e1e7
·
verified ·
1 Parent(s): f099dda

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -834
app.py DELETED
@@ -1,834 +0,0 @@
1
- import requests
2
- import json
3
- import gradio as gr
4
- from typing import Dict, Any, List, Optional
5
- import os
6
- from dataclasses import dataclass
7
- import re
8
- import pandas as pd
9
- import time
10
- import random
11
- from io import StringIO
12
- from pathlib import Path
13
-
14
- @dataclass
15
- class CerebrasConfig:
16
- """تنظیمات Cerebras API"""
17
- api_key: str
18
- base_url: str = "https://api.cerebras.ai/v1"
19
- model: str = "llama-3.3-70b"
20
- max_tokens: int = 2000
21
- temperature: float = 0.1
22
-
23
- # ============= تنظیمات Rate Limit =============
24
- # ⚠️ Cerebras Free Tier: حدود 30 request/minute
25
- DELAY_BETWEEN_REQUESTS = 8.0 # ⭐ ثانیه تاخیر بین هر درخواست (8 ثانیه = ~7.5 req/min)
26
- MAX_RETRIES = 5 # کاهش تعداد retry
27
- INITIAL_BACKOFF = 30.0 # ⭐ تاخیر اولیه بیشتر
28
- BACKOFF_MULTIPLIER = 1.5 # ⭐ ضریب کمتر
29
- MAX_BACKOFF = 120.0 # حداکثر تاخیر (2 دقیقه)
30
- CHECKPOINT_INTERVAL = 5 # هر چند ردیف یکبار ذخیره شود
31
-
32
- class AdvancedCerebrasAnonymizer:
33
- """سیستم پیشرفته ناشناس‌سازی متون مالی/خبری فارسی"""
34
-
35
- def __init__(self, api_key: str = None):
36
- if api_key is None:
37
- api_key = os.getenv("CEREBRAS_API_KEY")
38
- if not api_key:
39
- raise ValueError("کلید API یافت نشد")
40
-
41
- self.config = CerebrasConfig(api_key=api_key)
42
- self.system_prompt = self._create_advanced_system_prompt()
43
- self.last_request_time = 0
44
- self.request_count = 0
45
-
46
- def _create_advanced_system_prompt(self) -> str:
47
- """ایجاد دستورالعمل سیستمی پیشرفته برای Cerebras"""
48
- return """شما یک «ناشناس‌ساز متون مالی/خبری فارسی» هستید. وظیفه‌تان جایگزینی اسامی خاص و مقادیر عددی با شناسه‌های بی‌معناست.
49
-
50
- ## **قوانین اندیس‌گذاری - CRITICAL**
51
- ### **1. ترتیب شماره‌گذاری الزامی:**
52
- - شرکت‌ها: company-01, company-02, company-03, company-04, ... (پیوسته و بدون گپ)
53
- - اشخاص: person-01, person-02, person-03, ... (پیوسته و بدون گپ)
54
- - اعداد: amount-01, amount-02, amount-03, ... (پیوسته و بدون گپ)
55
- - درصدها: percent-01, percent-02, percent-03, ... (پیوسته و بدون گپ)
56
-
57
- ### **2. ثبات شناسه‌ها در متن:**
58
- - اگر "همراه اول" اول‌بار company-01 شد، در تمام متن همان باشد
59
- - اگر "مهدی احمدی" اول‌بار person-01 شد، در تمام متن همان باشد
60
-
61
- ### **3. تشخیص صحیح انواع:**
62
- **شرکت/سازمان:** همراه اول، بانک ملی، ایران‌خودرو، سایپا، بانک مرکزی، سامانه کدال، وزارت نفت، سازمان تنظیم مقررات رادیویی، سازمان تامین اجتماعی
63
- **⚠️ CRITICAL - گروه‌ها:** "گروه همراه اول"، "گروه اقتصادی آزادگان"، "گروه مالی صبا" → همه company-XX هستند (نه group-XX)
64
- **⚠️ CRITICAL - کلمات عمومی:** "سه شرکت دارویی"، "چند بانک"، "یک شرکت" → کلمات عمومی هستند، موجودیت نیستند (حفظ شوند)
65
- **⚠️ CRITICAL - نام‌های مستعار:** "فاما" همان "فولاد مبارکه اصفهان" است → هر دو company-01
66
- **شخص:** مهدی اخوان بهابادی، محمدرضا فرزین، ابوالفضل نجارزاده
67
- **عدد:** 37، 70، 677، 73.7، 178 (هر عددی)
68
- **درصد:** 37 درصدی، 15 درصدی، 53 درصد، 43%
69
-
70
- ## **مثال‌های صحیح:**
71
-
72
- ### **مثال 1 (الگوی کامل):**
73
- **ورودی:** مهدی اخوان بهابادی، مدیرعامل همراه اول، اعلام کرد درآمد عملیاتی شرکت با رشد 37 درصدی به 70 هزار و 677 میلیارد تومان رسیده است. سود خالص 7101 میلیارد تومان و تلفیقی گروه همراه اول 8003 میلیارد تومان شد.
74
- **خروجی صحیح:** person-01، مدیرعامل company-01، اعلام کرد درآمد عملیاتی شرکت با رشد percent-01 به amount-01 رسیده است. سود خالص amount-02 و تلفیقی گروه company-01 amount-03 شد.
75
-
76
- ### **مثال 2:**
77
- **ورودی:** بانک مرکزی و بانک ملی با همکاری محمدرضا فرزین، 60 درصد سپرده‌ها را مدیریت کردند.
78
- **خروجی:** company-01 و company-02 با همکاری person-01، percent-01 سپرده‌ها را مدیریت کردند.
79
-
80
- ## **موارد حفظ شده:**
81
- - تاریخ‌ها: 1404/04/23، 30 آذر 1403، پاییز 1401
82
- - فصل‌های سال: پاییز، بهار، تابستان، زمستان
83
- - عناوین شغلی: مدیرعامل، رئیس کل، مدیرکل
84
- - واحدها: میلیارد تومان، همت، ریال، ماه، سال
85
- - مکان‌ها: تهران، اصفهان، ایران
86
- - کلمات عمومی: "سه شرکت دارویی"، "چند بانک"، "یک شرکت"، "مراکز درمانی"
87
- - دوره‌های زمانی: "۵ ماهه سال"، "۹ ماهه"، "۳ ماهه اول"
88
-
89
- ## **ممنوع:**
90
- - کلمات انگلیسی اضافی
91
- - تغییر ساختار جمله
92
- - حذف یا اضافه کردن کلمات
93
- - استفاده از group-XX - همه گروه‌ها باید company-XX باشند
94
-
95
- **فقط متن ناشناس‌شده را برگردان - هیچ توضیح اضافی نیاز نیست.**
96
- """
97
-
98
- def _wait_for_rate_limit(self):
99
- """اطمینان از رعایت فاصله بین درخواست‌ها"""
100
- elapsed = time.time() - self.last_request_time
101
- if elapsed < DELAY_BETWEEN_REQUESTS:
102
- sleep_time = DELAY_BETWEEN_REQUESTS - elapsed
103
- print(f" ⏳ انتظار {sleep_time:.1f} ثانیه برای رعایت rate limit...")
104
- time.sleep(sleep_time)
105
-
106
- def _make_api_request(self, text: str) -> Dict[str, Any]:
107
- """ارسال درخواست به Cerebras API با retry logic پیشرفته"""
108
- headers = {
109
- "Authorization": f"Bearer {self.config.api_key}",
110
- "Content-Type": "application/json"
111
- }
112
-
113
- payload = {
114
- "messages": [
115
- {"role": "system", "content": self.system_prompt},
116
- {"role": "user", "content": text}
117
- ],
118
- "model": self.config.model,
119
- "temperature": self.config.temperature,
120
- "max_tokens": self.config.max_tokens
121
- }
122
-
123
- for attempt in range(MAX_RETRIES):
124
- try:
125
- # ⭐ رعایت فاصله بین درخواست‌ها
126
- self._wait_for_rate_limit()
127
-
128
- response = requests.post(
129
- f"{self.config.base_url}/chat/completions",
130
- headers=headers,
131
- json=payload,
132
- timeout=90 # افزایش timeout
133
- )
134
-
135
- # ⭐ بررسی خطای 429 قبل از raise_for_status
136
- if response.status_code == 429:
137
- raise requests.exceptions.HTTPError("429 Too Many Requests")
138
-
139
- response.raise_for_status()
140
-
141
- self.last_request_time = time.time()
142
- self.request_count += 1
143
-
144
- return response.json()
145
-
146
- except requests.exceptions.RequestException as e:
147
- error_str = str(e)
148
-
149
- # ⭐ بررسی خطای Rate Limit
150
- if "429" in error_str or "Too Many Requests" in error_str or response.status_code == 429:
151
- # محاسبه زمان انتظار با exponential backoff
152
- backoff = min(
153
- INITIAL_BACKOFF * (BACKOFF_MULTIPLIER ** attempt),
154
- MAX_BACKOFF
155
- )
156
- # ⭐ اضافه کردن jitter تصادفی
157
- jitter = random.uniform(0, backoff * 0.2)
158
- wait_time = backoff + jitter
159
-
160
- print(f" ⚠️ Rate Limit! تلاش {attempt + 1}/{MAX_RETRIES}")
161
- print(f" ⏳ انتظار {wait_time:.1f} ثانیه...")
162
- time.sleep(wait_time)
163
-
164
- if attempt == MAX_RETRIES - 1:
165
- raise Exception(f"خطا در ارتباط با Cerebras API پس از {MAX_RETRIES} تلاش: Rate Limit")
166
-
167
- # ⭐ خطای 503 (Service Unavailable)
168
- elif "503" in error_str or "Service Unavailable" in error_str:
169
- wait_time = INITIAL_BACKOFF * (attempt + 1)
170
- print(f" ⚠️ سرویس موقتاً در دسترس نیست. تلاش {attempt + 1}/{MAX_RETRIES}")
171
- print(f" ⏳ انتظار {wait_time:.1f} ثانیه...")
172
- time.sleep(wait_time)
173
-
174
- if attempt == MAX_RETRIES - 1:
175
- raise Exception(f"خطا: سرویس در دسترس نیست پس از {MAX_RETRIES} تلاش")
176
-
177
- # ⭐ خطای timeout
178
- elif "timeout" in error_str.lower() or "timed out" in error_str.lower():
179
- wait_time = 5 * (attempt + 1)
180
- print(f" ⚠️ Timeout! تلاش {attempt + 1}/{MAX_RETRIES}")
181
- time.sleep(wait_time)
182
-
183
- if attempt == MAX_RETRIES - 1:
184
- raise Exception(f"خطا: Timeout پس از {MAX_RETRIES} تلاش")
185
-
186
- else:
187
- # سایر خطاها
188
- if attempt < MAX_RETRIES - 1:
189
- print(f" ⚠️ خطا: {error_str[:80]}... تلاش مجدد...")
190
- time.sleep(INITIAL_BACKOFF)
191
- else:
192
- raise Exception(f"خطا در ارتباط با Cerebras API: {error_str}")
193
-
194
- raise Exception(f"ناموفق پس از {MAX_RETRIES} تلاش")
195
-
196
- def _clean_markdown(self, content: str) -> str:
197
- """پاک کردن markdown از پاسخ"""
198
- if "```" in content:
199
- lines = content.split('\n')
200
- clean_lines = []
201
- skip = False
202
- for line in lines:
203
- if line.strip().startswith('```'):
204
- skip = not skip
205
- continue
206
- if not skip:
207
- clean_lines.append(line)
208
- content = '\n'.join(clean_lines)
209
- return content
210
-
211
- def _analyze_anonymized_text(self, text: str) -> Dict[str, Any]:
212
- """تحلیل متن ناشناس‌سازی شده"""
213
- companies = re.findall(r'company-(\d+)', text)
214
- persons = re.findall(r'person-(\d+)', text)
215
- amounts = re.findall(r'amount-(\d+)', text)
216
- percents = re.findall(r'percent-(\d+)', text)
217
-
218
- statistics = {
219
- "company": len(set(companies)),
220
- "person": len(set(persons)),
221
- "amount": len(set(amounts)),
222
- "percent": len(set(percents)),
223
- "total_replacements": len(companies) + len(persons) + len(amounts) + len(percents)
224
- }
225
-
226
- entities = {
227
- "companies": sorted(list(set(companies)), key=lambda x: int(x)),
228
- "persons": sorted(list(set(persons)), key=lambda x: int(x)),
229
- "amounts": sorted(list(set(amounts)), key=lambda x: int(x)),
230
- "percents": sorted(list(set(percents)), key=lambda x: int(x))
231
- }
232
-
233
- detailed_analysis = {
234
- "preserved_dates": len(re.findall(r'\d{4}/\d{1,2}/\d{1,2}|\d{1,2}\s+\w+\s+\d{4}', text)),
235
- "preserved_times": len(re.findall(r'\d{1,2}:\d{2}', text)),
236
- "financial_indicators": len(re.findall(r'\b(EPS|P/E|ARPU|NPL|ROE|ROA)\b', text)),
237
- "units_preserved": len(re.findall(r'(میلیارد|میلیون|هزار|تومان|ریال|درهم|دلار|یورو|تن|کیلوگرم)', text))
238
- }
239
-
240
- return {
241
- "statistics": statistics,
242
- "entities": entities,
243
- "detailed_analysis": detailed_analysis
244
- }
245
-
246
- def _validate_anonymized_text(self, text: str) -> Dict[str, Any]:
247
- """اعتبارسنجی پیشرفته متن ناشناس‌شده"""
248
- companies = re.findall(r'company-(\d+)', text)
249
- persons = re.findall(r'person-(\d+)', text)
250
- amounts = re.findall(r'amount-(\d+)', text)
251
- percents = re.findall(r'percent-(\d+)', text)
252
-
253
- validation_issues = []
254
-
255
- for entity_type, indices in [
256
- ("company", companies),
257
- ("person", persons),
258
- ("amount", amounts),
259
- ("percent", percents)
260
- ]:
261
- if indices:
262
- unique_indices = sorted(list(set([int(x) for x in indices])))
263
- if unique_indices[0] != 1:
264
- validation_issues.append(f"اندیس {entity_type} از 01 شروع نشده")
265
- expected = list(range(1, len(unique_indices) + 1))
266
- if unique_indices != expected:
267
- validation_issues.append(f"اندیس‌های {entity_type} پیوسته نیستند")
268
-
269
- return {
270
- "is_valid": len(validation_issues) == 0,
271
- "issues": validation_issues,
272
- "entity_counts": {
273
- "company": len(set(companies)),
274
- "person": len(set(persons)),
275
- "amount": len(set(amounts)),
276
- "percent": len(set(percents))
277
- }
278
- }
279
-
280
- def anonymize_text(self, text: str) -> Dict[str, Any]:
281
- """ناشناس‌سازی متن با استفاده از Cerebras"""
282
- if not text or not text.strip():
283
- return {
284
- "success": False,
285
- "error": "متن ورودی خالی است"
286
- }
287
-
288
- try:
289
- response = self._make_api_request(text)
290
-
291
- if "choices" not in response or not response["choices"]:
292
- return {
293
- "success": False,
294
- "error": "پاسخ نامعتبر از API"
295
- }
296
-
297
- content = response["choices"][0]["message"]["content"]
298
- content = self._clean_markdown(content)
299
- content = content.strip()
300
-
301
- analysis = self._analyze_anonymized_text(content)
302
-
303
- return {
304
- "success": True,
305
- "anonymized_text": content,
306
- "entities": analysis["entities"],
307
- "statistics": analysis["statistics"],
308
- "detailed_analysis": analysis["detailed_analysis"],
309
- "usage": response.get("usage", {}),
310
- "quality_check": self._validate_anonymized_text(content)
311
- }
312
-
313
- except Exception as e:
314
- return {
315
- "success": False,
316
- "error": f"خطا در پردازش: {str(e)}"
317
- }
318
-
319
- def anonymize_batch(self, texts: List[str], progress_callback=None) -> List[Dict[str, Any]]:
320
- """ناشناس‌سازی دسته‌ای متون"""
321
- results = []
322
- total = len(texts)
323
-
324
- for idx, text in enumerate(texts):
325
- if progress_callback:
326
- progress_callback((idx + 1) / total, f"پردازش سطر {idx + 1} از {total}")
327
-
328
- result = self.anonymize_text(text)
329
- results.append(result)
330
-
331
- # تاخیر برای جلوگیری از rate limiting
332
- if idx < total - 1:
333
- time.sleep(DELAY_BETWEEN_REQUESTS)
334
-
335
- return results
336
-
337
-
338
- # ============= توابع Checkpoint =============
339
- def save_checkpoint(checkpoint_path: str, data: dict):
340
- """ذخیره checkpoint"""
341
- with open(checkpoint_path, 'w', encoding='utf-8') as f:
342
- json.dump(data, f, ensure_ascii=False, indent=2)
343
- print(f" 💾 Checkpoint ذخیره شد")
344
-
345
- def load_checkpoint(checkpoint_path: str) -> dict:
346
- """بارگذاری checkpoint"""
347
- if Path(checkpoint_path).exists():
348
- with open(checkpoint_path, 'r', encoding='utf-8') as f:
349
- return json.load(f)
350
- return None
351
-
352
-
353
- def create_advanced_interface():
354
- """ایجاد رابط کاربری پیشرفته با قابلیت پردازش دسته‌ای"""
355
-
356
- api_key_available = bool(os.getenv("CEREBRAS_API_KEY"))
357
-
358
- custom_css = """
359
- .gradio-container {
360
- font-family: 'Tahoma', 'Arial', sans-serif !important;
361
- direction: rtl;
362
- max-width: 1400px;
363
- margin: 0 auto;
364
- }
365
- .result-box {
366
- background-color: #f8f9fa;
367
- border: 2px solid #e9ecef;
368
- border-radius: 12px;
369
- padding: 20px;
370
- margin: 10px 0;
371
- }
372
- .warning-box {
373
- background-color: #fff3cd;
374
- border: 2px solid #ffeaa7;
375
- border-radius: 12px;
376
- padding: 15px;
377
- color: #856404;
378
- margin: 10px 0;
379
- }
380
- .success-box {
381
- background-color: #d4edda;
382
- border: 2px solid #c3e6cb;
383
- border-radius: 12px;
384
- padding: 15px;
385
- color: #155724;
386
- margin: 10px 0;
387
- }
388
- .batch-progress {
389
- background-color: #e3f2fd;
390
- border: 2px solid #90caf9;
391
- border-radius: 12px;
392
- padding: 15px;
393
- margin: 10px 0;
394
- }
395
- """
396
-
397
- with gr.Blocks(css=custom_css, title="ناشناس‌ساز پیشرفته متن فارسی با Cerebras", theme=gr.themes.Soft()) as interface:
398
-
399
- gr.Markdown("""
400
- # 🔒 سیستم پیشرفته ناشناس‌سازی متون مالی/خبری فارسی
401
- ### ⚡ قدرت‌گرفته از Cerebras AI - با مدیریت هوشمند Rate Limit
402
- """)
403
-
404
- # API Key input
405
- if api_key_available:
406
- gr.Markdown("""
407
- <div class="success-box">
408
- ✅ <strong>سیستم آماده است</strong> - کلید API تنظیم شده
409
- </div>
410
- """)
411
- api_key_input = gr.Textbox(visible=False, value="")
412
- else:
413
- gr.Markdown("""
414
- <div class="warning-box">
415
- ⚠️ <strong>کلید API تنظیم نشده</strong><br>
416
- لطفاً کلید Cerebras API خود را در زیر وارد کنید
417
- </div>
418
- """)
419
- api_key_input = gr.Textbox(
420
- label="🔑 کلید Cerebras API",
421
- placeholder="csk-...",
422
- type="password"
423
- )
424
-
425
- # تب‌های اصلی
426
- with gr.Tabs() as tabs:
427
-
428
- # ===============================
429
- # تب 1: پردازش تکی
430
- # ===============================
431
- with gr.TabItem("📝 پردازش تکی"):
432
- with gr.Row():
433
- with gr.Column(scale=1):
434
- input_text = gr.Textbox(
435
- label="📝 متن ورودی",
436
- placeholder="متن مالی یا خبری خود را اینجا وارد کنید...",
437
- lines=12,
438
- rtl=True
439
- )
440
- with gr.Row():
441
- anonymize_btn = gr.Button("🚀 ناشناس‌سازی", variant="primary", size="lg")
442
- clear_btn = gr.Button("🗑️ پاک کردن", variant="secondary")
443
-
444
- with gr.Column(scale=1):
445
- output_text = gr.Textbox(
446
- label="📤 متن ناشناس‌شده",
447
- lines=12,
448
- rtl=True,
449
- interactive=False
450
- )
451
- copy_btn = gr.Button("📋 کپی نتیجه", variant="secondary")
452
- copy_output = gr.Textbox(visible=False)
453
-
454
- # آمار و تحلیل
455
- with gr.Row():
456
- with gr.Column():
457
- statistics_output = gr.Markdown(label="📊 آمار")
458
- with gr.Column():
459
- quality_output = gr.Markdown(label="✅ کیفیت")
460
-
461
- with gr.Row():
462
- with gr.Column():
463
- entities_output = gr.Markdown(label="🏷️ موجودیت‌ها")
464
- with gr.Column():
465
- detailed_analysis_output = gr.Markdown(label="📈 تحلیل تفصیلی")
466
-
467
- usage_output = gr.Markdown(label="⚡ مصرف")
468
-
469
- # ===============================
470
- # تب 2: پردازش دسته‌ای
471
- # ===============================
472
- with gr.TabItem("📁 پردازش دسته‌ای"):
473
- gr.Markdown("""
474
- ### 📁 پردازش دسته‌ای فایل CSV
475
- ⚠️ **توجه:** برای جلوگیری از خطای Rate Limit، بین هر درخواست **4 ثانیه** تاخیر اعمال می‌شود.
476
-
477
- 💾 **قابلیت Checkpoint:** اگر پردازش قطع شود، می‌توانید از همان نقطه ادامه دهید.
478
- """)
479
-
480
- with gr.Row():
481
- with gr.Column():
482
- csv_file = gr.File(
483
- label="📂 آپلود فایل CSV",
484
- file_types=[".csv"]
485
- )
486
- text_column = gr.Dropdown(
487
- label="📝 ستون متن",
488
- choices=[],
489
- interactive=True
490
- )
491
- output_column_name = gr.Textbox(
492
- label="📤 نام ستون خروجی",
493
- value="anonymized_text",
494
- placeholder="anonymized_text"
495
- )
496
-
497
- # ⭐ گزینه ادامه از checkpoint
498
- continue_from_checkpoint = gr.Checkbox(
499
- label="📍 ادامه از checkpoint قبلی (اگر موجود باشد)",
500
- value=True
501
- )
502
-
503
- batch_btn = gr.Button("🚀 شروع پردازش", variant="primary", size="lg")
504
-
505
- with gr.Column():
506
- preview_df = gr.Dataframe(
507
- label="👁️ پیش‌نمایش فایل",
508
- interactive=False,
509
- wrap=True
510
- )
511
-
512
- batch_progress = gr.Markdown("📊 آماده پردازش...")
513
- batch_stats = gr.Markdown("")
514
-
515
- with gr.Row():
516
- result_df = gr.Dataframe(
517
- label="📊 نتایج پردازش",
518
- interactive=False,
519
- wrap=True
520
- )
521
-
522
- download_btn = gr.File(
523
- label="📥 دانلود نتایج",
524
- visible=False
525
- )
526
-
527
- error_log = gr.Markdown("")
528
-
529
- # ===============================
530
- # توابع پردازش
531
- # ===============================
532
-
533
- def load_csv_columns(file_path):
534
- """بارگذاری ستون‌های CSV"""
535
- if file_path is None:
536
- return gr.Dropdown(choices=[]), None, "📊 فایل انتخاب نشده"
537
-
538
- try:
539
- try:
540
- df = pd.read_csv(file_path, encoding='utf-8')
541
- except:
542
- try:
543
- df = pd.read_csv(file_path, encoding='utf-8-sig')
544
- except:
545
- df = pd.read_csv(file_path, encoding='cp1256')
546
-
547
- columns = df.columns.tolist()
548
- preview = df.head(5)
549
-
550
- return (
551
- gr.Dropdown(choices=columns, value=columns[0] if columns else None),
552
- preview,
553
- f"✅ ��ایل بارگذاری شد | {len(df)} سطر | {len(columns)} ستون"
554
- )
555
- except Exception as e:
556
- return gr.Dropdown(choices=[]), None, f"❌ خطا در بارگذاری: {str(e)}"
557
-
558
- def process_single_text(text, api_key):
559
- """پردازش تک متن"""
560
- if not text or not text.strip():
561
- return "", "⚠️ لطفاً متن وارد کنید", "", "", "", ""
562
-
563
- try:
564
- key = api_key if api_key else os.getenv("CEREBRAS_API_KEY")
565
- if not key:
566
- return "", "❌ کلید API تنظیم نشده", "", "", "", ""
567
-
568
- anonymizer = AdvancedCerebrasAnonymizer(api_key=key)
569
- result = anonymizer.anonymize_text(text)
570
-
571
- if not result["success"]:
572
- return "", f"❌ خطا: {result['error']}", "", "", "", ""
573
-
574
- # آمار
575
- stats = result["statistics"]
576
- stats_md = f"""
577
- ### 📊 آمار جایگزینی:
578
- | نوع | تعداد |
579
- |-----|-------|
580
- | شرکت | {stats['company']} |
581
- | شخص | {stats['person']} |
582
- | مبلغ | {stats['amount']} |
583
- | درصد | {stats['percent']} |
584
- | **کل** | **{stats['total_replacements']}** |
585
- """
586
-
587
- # کیفیت
588
- quality = result["quality_check"]
589
- quality_status = "✅ معتبر" if quality["is_valid"] else "⚠️ مشکل دارد"
590
- quality_md = f"### {quality_status}\n"
591
- if quality["issues"]:
592
- quality_md += "\n".join([f"- {issue}" for issue in quality["issues"]])
593
-
594
- # موجودیت‌ها
595
- entities = result["entities"]
596
- entities_md = f"""
597
- ### 🏷️ موجودیت‌های شناسایی شده:
598
- - **شرکت‌ها:** {', '.join([f'company-{x}' for x in entities['companies']]) or '-'}
599
- - **اشخاص:** {', '.join([f'person-{x}' for x in entities['persons']]) or '-'}
600
- - **مبالغ:** {', '.join([f'amount-{x}' for x in entities['amounts']]) or '-'}
601
- - **درصدها:** {', '.join([f'percent-{x}' for x in entities['percents']]) or '-'}
602
- """
603
-
604
- # تحلیل تفصیلی
605
- detailed = result["detailed_analysis"]
606
- detailed_md = f"""
607
- | شاخص | مقدار |
608
- |------|-------|
609
- | تاریخ‌های حفظ شده | {detailed['preserved_dates']} |
610
- | شاخص‌های مالی | {detailed['financial_indicators']} |
611
- | واحدهای حفظ شده | {detailed['units_preserved']} |
612
- """
613
-
614
- # مصرف
615
- usage = result.get("usage", {})
616
- usage_md = f"⚡ **توکن‌ها:** ورودی: {usage.get('prompt_tokens', '-')} | خروجی: {usage.get('completion_tokens', '-')}"
617
-
618
- return (
619
- result["anonymized_text"],
620
- stats_md,
621
- quality_md,
622
- entities_md,
623
- detailed_md,
624
- usage_md
625
- )
626
-
627
- except Exception as e:
628
- return "", f"❌ خطا: {str(e)}", "", "", "", ""
629
-
630
- def process_batch_csv(file_path, text_col, output_col, api_key, use_checkpoint, progress=gr.Progress()):
631
- """پردازش دسته‌ای فایل CSV با Checkpoint"""
632
- if file_path is None:
633
- return None, "❌ لطفاً فایل CSV آپلود کنید", "", gr.File(visible=False), None
634
-
635
- if not text_col:
636
- return None, "❌ لطفاً ستون متن را انتخاب کنید", "", gr.File(visible=False), None
637
-
638
- try:
639
- # خواندن فایل
640
- try:
641
- df = pd.read_csv(file_path, encoding='utf-8')
642
- except:
643
- try:
644
- df = pd.read_csv(file_path, encoding='utf-8-sig')
645
- except:
646
- df = pd.read_csv(file_path, encoding='cp1256')
647
-
648
- if text_col not in df.columns:
649
- return None, f"❌ ستون '{text_col}' در فایل یافت نشد", "", gr.File(visible=False), None
650
-
651
- # محدودیت تعداد سطرها
652
- max_rows = 1000
653
- if len(df) > max_rows:
654
- return None, f"❌ تعداد سطرها ({len(df)}) از حداکثر مجاز ({max_rows}) بیشتر است", "", gr.File(visible=False), None
655
-
656
- # ایجاد anonymizer
657
- key = api_key if api_key else os.getenv("CEREBRAS_API_KEY")
658
- if not key:
659
- return None, "❌ کلید API تنظیم نشده", "", gr.File(visible=False), None
660
-
661
- anonymizer = AdvancedCerebrasAnonymizer(api_key=key)
662
-
663
- # ⭐ بررسی checkpoint
664
- checkpoint_path = "/tmp/anonymizer_checkpoint.json"
665
- start_index = 0
666
- anonymized_texts = [""] * len(df)
667
- error_rows = []
668
- success_count = 0
669
-
670
- if use_checkpoint:
671
- checkpoint = load_checkpoint(checkpoint_path)
672
- if checkpoint:
673
- start_index = checkpoint.get('last_processed', -1) + 1
674
- anonymized_texts = checkpoint.get('results', [""] * len(df))
675
- success_count = checkpoint.get('success_count', 0)
676
- error_rows = checkpoint.get('errors', [])
677
- print(f"✅ ادامه از checkpoint - ردیف {start_index}")
678
- progress(start_index / len(df), desc=f"ادامه از ردیف {start_index}...")
679
-
680
- # پردازش سطرها
681
- total = len(df)
682
-
683
- progress(start_index / total, desc="شروع پردازش...")
684
-
685
- for idx in range(start_index, total):
686
- row = df.iloc[idx]
687
- text = str(row[text_col])
688
-
689
- progress((idx + 1) / total, desc=f"پردازش سطر {idx + 1} از {total} (⏱️ ~{DELAY_BETWEEN_REQUESTS}s/row)")
690
-
691
- if not text or text.strip() == '' or text.lower() == 'nan':
692
- anonymized_texts[idx] = ""
693
- continue
694
-
695
- result = anonymizer.anonymize_text(text)
696
-
697
- if result["success"]:
698
- anonymized_texts[idx] = result["anonymized_text"]
699
- success_count += 1
700
- else:
701
- anonymized_texts[idx] = f"[خطا: {result['error']}]"
702
- error_rows.append(f"سطر {idx + 1}: {result['error']}")
703
-
704
- # ⭐ ذخیره checkpoint هر چند ردیف
705
- if (idx + 1) % CHECKPOINT_INTERVAL == 0:
706
- save_checkpoint(checkpoint_path, {
707
- 'last_processed': idx,
708
- 'results': anonymized_texts,
709
- 'success_count': success_count,
710
- 'errors': error_rows,
711
- 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
712
- })
713
-
714
- # ⭐ تاخیر برای جلوگیری از rate limit (اعمال شده در _make_api_request)
715
-
716
- # ⭐ ذخیره checkpoint نهایی
717
- save_checkpoint(checkpoint_path, {
718
- 'last_processed': total - 1,
719
- 'results': anonymized_texts,
720
- 'success_count': success_count,
721
- 'errors': error_rows,
722
- 'completed': True,
723
- 'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
724
- })
725
-
726
- # اضافه کردن ستون جدید
727
- output_col_name = output_col if output_col else "anonymized_text"
728
- df[output_col_name] = anonymized_texts
729
-
730
- # ذخیره فایل خروجی
731
- output_path = "/tmp/anonymized_output.csv"
732
- df.to_csv(output_path, index=False, encoding='utf-8-sig')
733
-
734
- # آمار
735
- stats_md = f"""
736
- ### 📊 آمار پردازش:
737
- | شاخص | مقدار |
738
- |------|-------|
739
- | کل سطرها | {total} |
740
- | پردازش موفق | {success_count} |
741
- | خطا | {len(error_rows)} |
742
- | درصد موفقیت | {(success_count/total*100):.1f}% |
743
- | تاخیر بین درخواست‌ها | {DELAY_BETWEEN_REQUESTS} ثانیه |
744
- """
745
-
746
- # گزارش خطاها
747
- error_md = ""
748
- if error_rows:
749
- error_md = "### ⚠️ خطاهای مشاهده شده:\n" + "\n".join([f"- {e}" for e in error_rows[:20]])
750
- if len(error_rows) > 20:
751
- error_md += f"\n... و {len(error_rows) - 20} خطای دیگر"
752
-
753
- # نمایش نتایج
754
- result_preview = df[[text_col, output_col_name]].head(10)
755
-
756
- return (
757
- result_preview,
758
- f"✅ **پردازش کامل شد!** | {success_count} سطر با موفقیت",
759
- stats_md,
760
- gr.File(value=output_path, visible=True),
761
- error_md
762
- )
763
-
764
- except Exception as e:
765
- return None, f"❌ خطا در پردازش: {str(e)}", "", gr.File(visible=False), str(e)
766
-
767
- def copy_text(text_to_copy):
768
- """کپی متن"""
769
- if not text_to_copy or not text_to_copy.strip():
770
- return gr.Textbox(visible=False), "⚠️ متنی برای کپی وجود ندارد"
771
- return gr.Textbox(value=text_to_copy, visible=True), "✅ متن کپی شد"
772
-
773
- def clear_all():
774
- """پاک کردن فیلدها"""
775
- return "", "", "", "", "", "", "", gr.Textbox(visible=False)
776
-
777
- # ===============================
778
- # اتصال رویدادها
779
- # ===============================
780
-
781
- # پردازش تکی
782
- anonymize_btn.click(
783
- fn=process_single_text,
784
- inputs=[input_text, api_key_input],
785
- outputs=[output_text, statistics_output, quality_output, entities_output, detailed_analysis_output, usage_output]
786
- )
787
-
788
- copy_btn.click(
789
- fn=copy_text,
790
- inputs=[output_text],
791
- outputs=[copy_output, statistics_output]
792
- )
793
-
794
- clear_btn.click(
795
- fn=clear_all,
796
- outputs=[input_text, output_text, statistics_output, quality_output, entities_output, detailed_analysis_output, usage_output, copy_output]
797
- )
798
-
799
- # پردازش دسته‌ای
800
- csv_file.change(
801
- fn=load_csv_columns,
802
- inputs=[csv_file],
803
- outputs=[text_column, preview_df, batch_progress]
804
- )
805
-
806
- batch_btn.click(
807
- fn=process_batch_csv,
808
- inputs=[csv_file, text_column, output_column_name, api_key_input, continue_from_checkpoint],
809
- outputs=[result_df, batch_progress, batch_stats, download_btn, error_log]
810
- )
811
-
812
- # مثال‌ها
813
- gr.Examples(
814
- examples=[
815
- ["مهدی اخوان بهابادی، مدیرعامل همراه اول، اعلام کرد درآمد عملیاتی شرکت با رشد 37 درصدی به 70 هزار و 677 میلیارد تومان رسیده است."],
816
- ["بانک مرکزی و بانک ملی با همکاری محمدرضا فرزین، 60 درصد سپرده‌ها را مدیریت کردند."],
817
- ["سازمان تامین اجتماعی دارای سه شرکت دارویی است که از مراکز درمانی وابسته به وزارت بهداشت مطالباتی دارند."]
818
- ],
819
- inputs=input_text,
820
- label="📚 مثال‌ها"
821
- )
822
-
823
- return interface
824
-
825
-
826
- # اجرای برنامه
827
- if __name__ == "__main__":
828
- interface = create_advanced_interface()
829
- interface.launch(
830
- server_name="0.0.0.0",
831
- server_port=7860,
832
- share=True,
833
- show_error=True
834
- )