leilaghomashchi commited on
Commit
3511f32
·
verified ·
1 Parent(s): 5866bbb

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -946
app.py DELETED
@@ -1,946 +0,0 @@
1
- import requests
2
- import json
3
- import gradio as gr
4
- from typing import Dict, Any, List, Generator
5
- import os
6
- from dataclasses import dataclass
7
- import re
8
- import pandas as pd
9
- import time
10
- from datetime import datetime
11
- import threading
12
- from queue import Queue
13
- import io
14
-
15
- @dataclass
16
- class CerebrasConfig:
17
- """تنظیمات Cerebras API"""
18
- api_key: str
19
- base_url: str = "https://api.cerebras.ai/v1"
20
- model: str = "qwen-3-32b"
21
- max_tokens: int = 2000
22
- temperature: float = 0.1
23
-
24
- @dataclass
25
- class RateLimitConfig:
26
- """تنظیمات محدودیت نرخ درخواست برای Cerebras"""
27
- # محدودیت‌های Cerebras Free Tier
28
- requests_per_minute: int = 30 # حداکثر درخواست در دقیقه
29
- tokens_per_minute: int = 60000 # حداکثر توکن در دقیقه
30
- min_delay_between_requests: float = 2.0 # حداقل تأخیر بین درخواست‌ها (ثانیه)
31
- max_retries: int = 5 # حداکثر تلاش مجدد
32
- initial_backoff: float = 5.0 # تأخیر اولیه برای backoff (ثانیه)
33
- max_backoff: float = 120.0 # حداکثر تأخیر backoff (ثانیه)
34
- backoff_multiplier: float = 2.0 # ضریب افزایش تأخیر
35
-
36
- class RateLimiter:
37
- """مدیریت محدودیت نرخ درخواست"""
38
-
39
- def __init__(self, config: RateLimitConfig):
40
- self.config = config
41
- self.request_times: List[float] = []
42
- self.lock = threading.Lock()
43
- self.consecutive_failures = 0
44
-
45
- def wait_if_needed(self) -> float:
46
- """انتظار تا زمان مجاز ارسال درخواست بعدی"""
47
- with self.lock:
48
- now = time.time()
49
-
50
- # پاک کردن درخواست‌های قدیمی‌تر از 1 دقیقه
51
- self.request_times = [t for t in self.request_times if now - t < 60]
52
-
53
- # محاسبه زمان انتظار
54
- wait_time = 0.0
55
-
56
- # اگر به محدودیت درخواست در دقیقه رسیده‌ایم
57
- if len(self.request_times) >= self.config.requests_per_minute:
58
- oldest_request = min(self.request_times)
59
- wait_time = max(wait_time, 60 - (now - oldest_request) + 1)
60
-
61
- # حداقل تأخیر بین درخواست‌ها
62
- if self.request_times:
63
- time_since_last = now - max(self.request_times)
64
- if time_since_last < self.config.min_delay_between_requests:
65
- wait_time = max(wait_time, self.config.min_delay_between_requests - time_since_last)
66
-
67
- # افزایش تأخیر در صورت خطاهای متوالی
68
- if self.consecutive_failures > 0:
69
- failure_wait = min(
70
- self.config.initial_backoff * (self.config.backoff_multiplier ** self.consecutive_failures),
71
- self.config.max_backoff
72
- )
73
- wait_time = max(wait_time, failure_wait)
74
-
75
- if wait_time > 0:
76
- time.sleep(wait_time)
77
-
78
- self.request_times.append(time.time())
79
- return wait_time
80
-
81
- def report_success(self):
82
- """گزارش موفقیت درخواست"""
83
- with self.lock:
84
- self.consecutive_failures = 0
85
-
86
- def report_failure(self, is_rate_limit: bool = False):
87
- """گزارش شکست درخواست"""
88
- with self.lock:
89
- if is_rate_limit:
90
- self.consecutive_failures += 1
91
- else:
92
- # برای خطاهای غیر rate limit، کمتر افزایش می‌دهیم
93
- self.consecutive_failures = min(self.consecutive_failures + 0.5, 3)
94
-
95
- def get_estimated_wait_time(self) -> float:
96
- """تخمین زمان انتظار برای درخواست بعدی"""
97
- with self.lock:
98
- now = time.time()
99
- self.request_times = [t for t in self.request_times if now - t < 60]
100
-
101
- if len(self.request_times) >= self.config.requests_per_minute:
102
- oldest_request = min(self.request_times)
103
- return max(0, 60 - (now - oldest_request) + 1)
104
-
105
- return self.config.min_delay_between_requests
106
-
107
- class AdvancedCerebrasAnonymizer:
108
- """سیستم پیشرفته ناشناس‌سازی متون مالی/خبری فارسی"""
109
-
110
- def __init__(self, api_key: str = None, rate_limit_config: RateLimitConfig = None):
111
- if api_key is None:
112
- api_key = os.getenv("CEREBRAS_API_KEY")
113
- if not api_key:
114
- raise ValueError("کلید API یافت نشد")
115
-
116
- self.config = CerebrasConfig(api_key=api_key)
117
- self.rate_limit_config = rate_limit_config or RateLimitConfig()
118
- self.rate_limiter = RateLimiter(self.rate_limit_config)
119
- self.system_prompt = self._create_advanced_system_prompt()
120
-
121
- def _create_advanced_system_prompt(self) -> str:
122
- """ایجاد دستورالعمل سیستمی پیشرفته برای Cerebras"""
123
- return """شما یک «ناشناس‌ساز متون مالی/خبری فارسی» هستید. وظیفه‌تان جایگزینی اسامی خاص و مقادیر عددی با شناسه‌های بی‌معناست.
124
-
125
- ## **قوانین اندیس‌گذاری - CRITICAL**
126
- ### **1. ترتیب شماره‌گذاری الزامی:**
127
- - شرکت‌ها: company-01, company-02, company-03, company-04, ... (پیوسته و بدون گپ)
128
- - اشخاص: person-01, person-02, person-03, ... (پیوسته و بدون گپ)
129
- - اعداد: amount-01, amount-02, amount-03, ... (پیوسته و بدون گپ)
130
- - درصدها: percent-01, percent-02, percent-03, ... (پیوسته و بدون گپ)
131
-
132
- ### **2. ثبات شناسه‌ها در متن:**
133
- - اگر "همراه اول" اول‌بار company-01 شد، در تمام متن همان باشد
134
- - اگر "مهدی احمدی" اول‌بار person-01 شد، در تمام متن همان باشد
135
-
136
- ### **3. تشخیص صحیح انواع:**
137
- **شرکت/سازمان:** همراه اول، بانک ملی، ایران‌خودرو، سایپا، بانک مرکزی، سامانه کدال، وزارت نفت، سازمان تنظیم مقررات رادیویی، سازمان تامین اجتماعی
138
- **⚠️ CRITICAL - گروه‌ها:** "گروه همراه اول"، "گروه اقتصادی آزادگان"، "گروه مالی صبا" → همه company-XX هستند (نه group-XX)
139
- **⚠️ CRITICAL - کلمات عمومی:** "سه شرکت دارویی"، "چند بانک"، "یک شرکت" → کلمات عمومی هستند، موجودیت نیستند (حفظ شوند)
140
- **⚠️ CRITICAL - نام‌های مستعار:** "فاما" همان "فولاد مبارکه اصفهان" است → هر دو company-01
141
- **شخص:** مهدی اخوان بهابادی، محمدرضا فرزین، ابوالفضل نجارزاده
142
- **عدد:** 37، 70، 677، 73.7، 178 (هر عددی)
143
- **درصد:** 37 درصدی، 15 درصدی، 53 درصد، 43%
144
-
145
- ## **مثال‌های صحیح:**
146
-
147
- ### **مثال 1 (الگوی کامل):**
148
- **ورودی:** مهدی اخوان بهابادی، مدیرعامل همراه اول، اعلام کرد درآمد عملیاتی شرکت با رشد 37 درصدی به 70 هزار و 677 میلیارد تومان رسیده است.
149
- **خروجی صحیح:** person-01، مدیرعامل company-01، اعلام کرد درآمد عملیاتی شرکت با رشد percent-01 به amount-01 رسیده است.
150
-
151
- ### **مثال 2:**
152
- **ورودی:** بانک مرکزی و بانک ملی با همکاری محمدرضا فرزین، 60 درصد سپرده‌ها را مدیریت کردند.
153
- **خروجی:** company-01 و company-02 با همکاری person-01، percent-01 سپرده‌ها را مدیریت کردند.
154
-
155
- ## **⚠️ CRITICAL - دوره‌های زمانی را حفظ کن:**
156
- - "۹ ماهه" → حفظ شود (نه amount-XX)
157
- - "۵ ماهه سال" → حفظ شود (نه amount-XX)
158
- - "۳ ماهه اول" → حفظ شود (نه amount-XX)
159
-
160
- ## **موارد حفظ شده:**
161
- - تاریخ‌ها: 1404/04/23، 30 آذر 1403، پاییز 1401
162
- - فصل‌های سال: پاییز، بهار، تابستان، زمستان
163
- - عناوین شغلی: مدیرعامل، رئیس کل، مدیرکل
164
- - واحدها: میلیارد تومان، همت، ریال، ماه، سال
165
- - مکان‌ها: تهران، اصفهان، ایران
166
-
167
- **فقط متن ناشناس‌شده را برگردان - هیچ توضیح اضافی نیاز نیست.**
168
- """
169
-
170
- def _make_api_request_with_retry(self, text: str) -> Dict[str, Any]:
171
- """ارسال درخواست به Cerebras API با مدیریت rate limit و retry"""
172
- headers = {
173
- "Authorization": f"Bearer {self.config.api_key}",
174
- "Content-Type": "application/json"
175
- }
176
-
177
- payload = {
178
- "messages": [
179
- {"role": "system", "content": self.system_prompt},
180
- {"role": "user", "content": text}
181
- ],
182
- "model": self.config.model,
183
- "temperature": self.config.temperature,
184
- "max_tokens": self.config.max_tokens
185
- }
186
-
187
- last_error = None
188
-
189
- for attempt in range(self.rate_limit_config.max_retries):
190
- # انتظار قبل از ارسال درخواست
191
- wait_time = self.rate_limiter.wait_if_needed()
192
-
193
- try:
194
- response = requests.post(
195
- f"{self.config.base_url}/chat/completions",
196
- headers=headers,
197
- json=payload,
198
- timeout=60
199
- )
200
-
201
- # بررسی خطای rate limit (429)
202
- if response.status_code == 429:
203
- self.rate_limiter.report_failure(is_rate_limit=True)
204
-
205
- # استخراج زمان انتظار از هدر (اگر موجود باشد)
206
- retry_after = response.headers.get('Retry-After')
207
- if retry_after:
208
- wait_seconds = int(retry_after)
209
- else:
210
- # محاسبه exponential backoff
211
- wait_seconds = min(
212
- self.rate_limit_config.initial_backoff * (self.rate_limit_config.backoff_multiplier ** attempt),
213
- self.rate_limit_config.max_backoff
214
- )
215
-
216
- last_error = f"محدودیت نرخ درخواست (429). تلاش {attempt + 1}/{self.rate_limit_config.max_retries}. انتظار {wait_seconds:.1f} ثانیه..."
217
- time.sleep(wait_seconds)
218
- continue
219
-
220
- response.raise_for_status()
221
- self.rate_limiter.report_success()
222
- return response.json()
223
-
224
- except requests.exceptions.Timeout:
225
- self.rate_limiter.report_failure(is_rate_limit=False)
226
- last_error = f"خطای timeout. تلاش {attempt + 1}/{self.rate_limit_config.max_retries}"
227
- time.sleep(self.rate_limit_config.initial_backoff)
228
-
229
- except requests.exceptions.RequestException as e:
230
- self.rate_limiter.report_failure(is_rate_limit=False)
231
- last_error = f"خطای شبکه: {str(e)}. تلاش {attempt + 1}/{self.rate_limit_config.max_retries}"
232
- time.sleep(self.rate_limit_config.initial_backoff)
233
-
234
- raise Exception(f"ناموفق پس از {self.rate_limit_config.max_retries} تلاش. آخرین خطا: {last_error}")
235
-
236
- def anonymize_text(self, text: str) -> Dict[str, Any]:
237
- """ناشناس‌سازی متن با استفاده از Cerebras"""
238
- if not text or not text.strip():
239
- return {
240
- "success": False,
241
- "error": "متن ورودی خالی است",
242
- "anonymized_text": ""
243
- }
244
-
245
- try:
246
- response = self._make_api_request_with_retry(text)
247
-
248
- if "choices" not in response or not response["choices"]:
249
- return {
250
- "success": False,
251
- "error": "پاسخ نامعتبر از API",
252
- "anonymized_text": ""
253
- }
254
-
255
- content = response["choices"][0]["message"]["content"]
256
- content = self._clean_markdown(content)
257
- content = content.strip()
258
-
259
- analysis = self._analyze_anonymized_text(content)
260
-
261
- return {
262
- "success": True,
263
- "anonymized_text": content,
264
- "entities": analysis["entities"],
265
- "statistics": analysis["statistics"],
266
- "usage": response.get("usage", {})
267
- }
268
-
269
- except Exception as e:
270
- return {
271
- "success": False,
272
- "error": f"خطا در پردازش: {str(e)}",
273
- "anonymized_text": ""
274
- }
275
-
276
- def _clean_markdown(self, content: str) -> str:
277
- """پاک کردن markdown از پاسخ"""
278
- if "```" in content:
279
- lines = content.split('\n')
280
- clean_lines = []
281
- skip = False
282
- for line in lines:
283
- if line.strip().startswith('```'):
284
- skip = not skip
285
- continue
286
- if not skip:
287
- clean_lines.append(line)
288
- content = '\n'.join(clean_lines)
289
- return content
290
-
291
- def _analyze_anonymized_text(self, text: str) -> Dict[str, Any]:
292
- """تحلیل متن ناشناس‌سازی شده"""
293
- companies = re.findall(r'company-(\d+)', text)
294
- persons = re.findall(r'person-(\d+)', text)
295
- amounts = re.findall(r'amount-(\d+)', text)
296
- percents = re.findall(r'percent-(\d+)', text)
297
-
298
- statistics = {
299
- "company": len(set(companies)),
300
- "person": len(set(persons)),
301
- "amount": len(set(amounts)),
302
- "percent": len(set(percents)),
303
- "total_replacements": len(companies) + len(persons) + len(amounts) + len(percents)
304
- }
305
-
306
- entities = {
307
- "companies": sorted(list(set(companies)), key=lambda x: int(x)),
308
- "persons": sorted(list(set(persons)), key=lambda x: int(x)),
309
- "amounts": sorted(list(set(amounts)), key=lambda x: int(x)),
310
- "percents": sorted(list(set(percents)), key=lambda x: int(x))
311
- }
312
-
313
- return {
314
- "statistics": statistics,
315
- "entities": entities
316
- }
317
-
318
-
319
- class BatchProcessor:
320
- """پردازشگر دسته‌ای فایل‌های CSV"""
321
-
322
- def __init__(self, api_key: str, rate_limit_config: RateLimitConfig = None):
323
- self.api_key = api_key
324
- self.rate_limit_config = rate_limit_config or RateLimitConfig()
325
- self.anonymizer = None
326
- self.is_cancelled = False
327
- self.current_progress = 0
328
- self.total_rows = 0
329
- self.processed_rows = 0
330
- self.failed_rows = 0
331
- self.start_time = None
332
-
333
- def cancel(self):
334
- """لغو پردازش"""
335
- self.is_cancelled = True
336
-
337
- def reset(self):
338
- """بازنشانی وضعیت"""
339
- self.is_cancelled = False
340
- self.current_progress = 0
341
- self.total_rows = 0
342
- self.processed_rows = 0
343
- self.failed_rows = 0
344
- self.start_time = None
345
-
346
- def process_csv(
347
- self,
348
- file_path: str,
349
- text_column: str,
350
- output_column: str = "anonymized_text",
351
- progress_callback=None
352
- ) -> Generator[Dict[str, Any], None, None]:
353
- """پردازش فایل CSV به صورت streaming"""
354
-
355
- self.reset()
356
- self.start_time = time.time()
357
-
358
- # خواندن فایل CSV
359
- try:
360
- df = pd.read_csv(file_path, encoding='utf-8')
361
- except UnicodeDecodeError:
362
- try:
363
- df = pd.read_csv(file_path, encoding='utf-8-sig')
364
- except:
365
- df = pd.read_csv(file_path, encoding='cp1256')
366
-
367
- if text_column not in df.columns:
368
- yield {
369
- "type": "error",
370
- "message": f"ستون '{text_column}' در فایل یافت نشد. ستون‌های موجود: {list(df.columns)}"
371
- }
372
- return
373
-
374
- self.total_rows = len(df)
375
-
376
- # ایجاد anonymizer
377
- self.anonymizer = AdvancedCerebrasAnonymizer(
378
- api_key=self.api_key,
379
- rate_limit_config=self.rate_limit_config
380
- )
381
-
382
- # ایجاد ستون خروجی
383
- df[output_column] = ""
384
- df["anonymization_status"] = ""
385
- df["entities_found"] = ""
386
-
387
- yield {
388
- "type": "info",
389
- "message": f"🚀 شروع پردازش {self.total_rows} ردیف...",
390
- "total": self.total_rows
391
- }
392
-
393
- results = []
394
-
395
- for idx, row in df.iterrows():
396
- if self.is_cancelled:
397
- yield {
398
- "type": "cancelled",
399
- "message": "پردازش توسط کاربر لغو شد",
400
- "processed": self.processed_rows,
401
- "failed": self.failed_rows
402
- }
403
- break
404
-
405
- text = str(row[text_column]) if pd.notna(row[text_column]) else ""
406
-
407
- if not text.strip():
408
- df.at[idx, output_column] = ""
409
- df.at[idx, "anonymization_status"] = "خالی"
410
- df.at[idx, "entities_found"] = ""
411
- self.processed_rows += 1
412
- continue
413
-
414
- # پردازش متن
415
- result = self.anonymizer.anonymize_text(text)
416
-
417
- if result["success"]:
418
- df.at[idx, output_column] = result["anonymized_text"]
419
- df.at[idx, "anonymization_status"] = "موفق"
420
- stats = result.get("statistics", {})
421
- entities_summary = f"شرکت:{stats.get('company',0)} | شخص:{stats.get('person',0)} | مبلغ:{stats.get('amount',0)} | درصد:{stats.get('percent',0)}"
422
- df.at[idx, "entities_found"] = entities_summary
423
- self.processed_rows += 1
424
- else:
425
- df.at[idx, output_column] = f"خطا: {result.get('error', 'نامشخص')}"
426
- df.at[idx, "anonymization_status"] = "ناموفق"
427
- df.at[idx, "entities_found"] = ""
428
- self.failed_rows += 1
429
-
430
- # محاسبه پیشرفت و زمان باقیمانده
431
- self.current_progress = (idx + 1) / self.total_rows * 100
432
- elapsed = time.time() - self.start_time
433
- avg_time_per_row = elapsed / (idx + 1)
434
- remaining_rows = self.total_rows - (idx + 1)
435
- estimated_remaining = avg_time_per_row * remaining_rows
436
-
437
- # تخمین زمان انتظار بعدی
438
- next_wait = self.anonymizer.rate_limiter.get_estimated_wait_time()
439
-
440
- yield {
441
- "type": "progress",
442
- "current": idx + 1,
443
- "total": self.total_rows,
444
- "progress": self.current_progress,
445
- "processed": self.processed_rows,
446
- "failed": self.failed_rows,
447
- "elapsed": elapsed,
448
- "estimated_remaining": estimated_remaining,
449
- "next_wait": next_wait,
450
- "last_result": result
451
- }
452
-
453
- # ذخیره نتیجه نهایی
454
- if not self.is_cancelled:
455
- output_path = file_path.replace('.csv', '_anonymized.csv')
456
- if output_path == file_path:
457
- output_path = file_path + '_anonymized.csv'
458
-
459
- df.to_csv(output_path, index=False, encoding='utf-8-sig')
460
-
461
- total_time = time.time() - self.start_time
462
-
463
- yield {
464
- "type": "complete",
465
- "message": "✅ پردازش با موفقیت تکمیل شد!",
466
- "output_path": output_path,
467
- "total": self.total_rows,
468
- "processed": self.processed_rows,
469
- "failed": self.failed_rows,
470
- "total_time": total_time,
471
- "dataframe": df
472
- }
473
-
474
-
475
- def create_batch_interface():
476
- """ایجاد رابط کاربری برای پردازش دسته‌ای"""
477
-
478
- api_key_available = bool(os.getenv("CEREBRAS_API_KEY"))
479
-
480
- custom_css = """
481
- .gradio-container {
482
- font-family: 'Tahoma', 'Arial', sans-serif !important;
483
- direction: rtl;
484
- max-width: 1400px;
485
- margin: 0 auto;
486
- }
487
- .progress-bar {
488
- background-color: #e9ecef;
489
- border-radius: 10px;
490
- height: 30px;
491
- overflow: hidden;
492
- }
493
- .progress-fill {
494
- background: linear-gradient(90deg, #28a745, #20c997);
495
- height: 100%;
496
- transition: width 0.3s ease;
497
- }
498
- .stats-card {
499
- background-color: #f8f9fa;
500
- border-radius: 10px;
501
- padding: 15px;
502
- margin: 10px 0;
503
- border: 1px solid #dee2e6;
504
- }
505
- .warning-box {
506
- background-color: #fff3cd;
507
- border: 2px solid #ffeaa7;
508
- border-radius: 12px;
509
- padding: 15px;
510
- color: #856404;
511
- margin: 10px 0;
512
- }
513
- .success-box {
514
- background-color: #d4edda;
515
- border: 2px solid #c3e6cb;
516
- border-radius: 12px;
517
- padding: 15px;
518
- color: #155724;
519
- margin: 10px 0;
520
- }
521
- .info-box {
522
- background-color: #d1ecf1;
523
- border: 2px solid #bee5eb;
524
- border-radius: 12px;
525
- padding: 15px;
526
- color: #0c5460;
527
- margin: 10px 0;
528
- }
529
- """
530
-
531
- # متغیرهای سراسری برای مدیریت پردازش
532
- batch_processor = {"instance": None}
533
-
534
- with gr.Blocks(css=custom_css, title="پردازش دسته‌ای ناشناس‌سازی با Cerebras", theme=gr.themes.Soft()) as interface:
535
-
536
- gr.Markdown("""
537
- # 🔒 سیستم پردازش دسته‌ای ناشناس‌سازی متون فارسی
538
- ### ⚡ قدرت‌گرفته از Cerebras AI با مدیریت هوشمند Rate Limit
539
- """)
540
-
541
- with gr.Tabs():
542
- # تب پردازش تکی
543
- with gr.Tab("📝 پردازش تکی"):
544
- if api_key_available:
545
- gr.Markdown('<div class="success-box">✅ <strong>سیستم آماده است</strong> - کلید API تنظیم شده</div>')
546
- single_api_key = gr.Textbox(visible=False, value="")
547
- else:
548
- gr.Markdown('<div class="warning-box">⚠️ <strong>کلید API تنظیم نشده</strong> - لطفاً کلید خود را وارد کنید</div>')
549
- single_api_key = gr.Textbox(label="🔑 کلید Cerebras API", placeholder="csk-...", type="password")
550
-
551
- with gr.Row():
552
- with gr.Column():
553
- single_input = gr.Textbox(label="📝 متن ورودی", placeholder="متن خود را وارد کنید...", lines=10)
554
- single_btn = gr.Button("🔒 ناشناس‌سازی", variant="primary")
555
- with gr.Column():
556
- single_output = gr.Textbox(label="🎯 متن ناشناس‌سازی شده", lines=10)
557
- single_stats = gr.Markdown()
558
-
559
- # تب پردازش دسته‌ای
560
- with gr.Tab("📁 پردازش دسته‌ای CSV"):
561
- gr.Markdown("""
562
- <div class="info-box">
563
- 📌 <strong>راهنمای پردازش دسته‌ای:</strong><br>
564
- 1. فایل CSV خود را آپلود کنید<br>
565
- 2. ستون حاوی متن را انتخاب کنید<br>
566
- 3. تنظیمات Rate Limit را بررسی کنید<br>
567
- 4. پردازش را شروع کنید<br><br>
568
- ⚠️ <strong>نکته مهم:</strong> برای جلوگیری از خطای 429، تأخیر بین درخواست‌ها به صورت خودکار مدیریت می‌شود.
569
- </div>
570
- """)
571
-
572
- if not api_key_available:
573
- batch_api_key = gr.Textbox(label="🔑 کلید Cerebras API", placeholder="csk-...", type="password")
574
- else:
575
- batch_api_key = gr.Textbox(visible=False, value="")
576
-
577
- with gr.Row():
578
- with gr.Column(scale=2):
579
- csv_file = gr.File(label="📂 فایل CSV", file_types=[".csv"])
580
-
581
- with gr.Row():
582
- text_column = gr.Dropdown(
583
- label="📑 ستون متن",
584
- choices=[],
585
- interactive=True,
586
- info="ستون حاوی متن برای ناشناس‌سازی"
587
- )
588
- output_column = gr.Textbox(
589
- label="📤 نام ستون خروجی",
590
- value="anonymized_text",
591
- info="نام ستون برای ذخیره نتایج"
592
- )
593
-
594
- with gr.Column(scale=1):
595
- gr.Markdown("### ⚙️ تنظیمات Rate Limit")
596
-
597
- delay_between_requests = gr.Slider(
598
- label="⏱️ حداقل تأخیر بین درخواست‌ها (ثانیه)",
599
- minimum=1.0,
600
- maximum=10.0,
601
- value=2.5,
602
- step=0.5,
603
- info="افزایش این مقدار از خطای 429 جلوگیری می‌کند"
604
- )
605
-
606
- requests_per_minute = gr.Slider(
607
- label="📊 حداکثر درخواست در دقیقه",
608
- minimum=5,
609
- maximum=30,
610
- value=20,
611
- step=1,
612
- info="محدودیت Cerebras Free: 30 درخواست/دقیقه"
613
- )
614
-
615
- max_retries = gr.Slider(
616
- label="🔄 حداکثر تلاش مجدد",
617
- minimum=1,
618
- maximum=10,
619
- value=5,
620
- step=1,
621
- info="تعداد تلاش در صورت خطای 429"
622
- )
623
-
624
- with gr.Row():
625
- start_btn = gr.Button("🚀 شروع پردازش", variant="primary", size="lg")
626
- cancel_btn = gr.Button("⏹️ لغو پردازش", variant="stop", size="lg")
627
-
628
- # نمایش پیشرفت
629
- progress_bar = gr.Slider(
630
- label="📊 پیشرفت کلی",
631
- minimum=0,
632
- maximum=100,
633
- value=0,
634
- interactive=False
635
- )
636
-
637
- with gr.Row():
638
- with gr.Column():
639
- progress_text = gr.Markdown("### 📈 وضعیت پردازش\nدر انتظار شروع...")
640
- with gr.Column():
641
- time_stats = gr.Markdown("### ⏱️ زمان‌بندی\nدر انتظار شروع...")
642
-
643
- # لاگ پردازش
644
- process_log = gr.Textbox(
645
- label="📋 لاگ پردازش",
646
- lines=8,
647
- max_lines=15,
648
- interactive=False
649
- )
650
-
651
- # نمایش نمونه نتایج
652
- with gr.Accordion("👁️ پیش‌نمایش نتایج", open=False):
653
- preview_table = gr.Dataframe(
654
- label="نمونه نتایج",
655
- headers=["متن اصلی", "متن ناشناس‌شده", "وضعیت"],
656
- interactive=False
657
- )
658
-
659
- # دانلود نتیجه
660
- output_file = gr.File(label="📥 دانلود فایل خروجی", visible=False)
661
-
662
- # تب تنظیمات
663
- with gr.Tab("⚙️ تنظیمات و راهنما"):
664
- gr.Markdown("""
665
- ## 📖 راهنمای محدودیت‌های Cerebras API
666
-
667
- ### 🔒 محدودیت‌های Free Tier:
668
- | پارامتر | مقدار |
669
- |---------|--------|
670
- | درخواست در دقیقه | 30 |
671
- | توکن در دقیقه | 60,000 |
672
- | توکن در روز | 1,000,000 |
673
-
674
- ### ⚡ نکات بهینه‌سازی:
675
- 1. **تأخیر بین درخواست‌ها:** حداقل 2 ثانیه بین هر درخواست
676
- 2. **Exponential Backoff:** در صورت خطای 429، تأخیر به صورت نمایی افزایش می‌یابد
677
- 3. **Retry Logic:** سیستم تا 5 بار تلاش مجدد می‌کند
678
-
679
- ### 🎯 پیشنهادات:
680
- - برای فایل‌های بزرگ (>100 ردیف)، تأخیر را روی 3 ثانیه تنظیم کنید
681
- - اگر خطای 429 زیاد دیدید، تأخیر را افزایش دهید
682
- - در ساعات شلوغ، محدودیت‌ها ممکن است سخت‌تر شوند
683
-
684
- ### 📊 فرمت فایل CSV:
685
- - **Encoding:** UTF-8 یا UTF-8-BOM پیشنهاد می‌شود
686
- - **ستون‌ها:** حداقل یک ستون حاوی متن فارسی
687
- - **حجم:** بدون محدودیت (اما پردازش فایل‌های بزرگ زمان‌بر است)
688
- """)
689
-
690
- # توابع
691
- def update_columns(file):
692
- """بروزرسانی لیست ستون‌ها بعد از آپلود فایل"""
693
- if file is None:
694
- return gr.update(choices=[], value=None)
695
-
696
- try:
697
- df = pd.read_csv(file.name, encoding='utf-8', nrows=5)
698
- except:
699
- try:
700
- df = pd.read_csv(file.name, encoding='utf-8-sig', nrows=5)
701
- except:
702
- df = pd.read_csv(file.name, encoding='cp1256', nrows=5)
703
-
704
- columns = list(df.columns)
705
- return gr.update(choices=columns, value=columns[0] if columns else None)
706
-
707
- def process_single_text(text, api_key):
708
- """پردازش متن تکی"""
709
- if not text.strip():
710
- return "", "⚠️ متن ورودی خالی است"
711
-
712
- key = api_key if api_key else os.getenv("CEREBRAS_API_KEY")
713
- if not key:
714
- return "", "❌ کلید API وارد نشده است"
715
-
716
- try:
717
- anonymizer = AdvancedCerebrasAnonymizer(api_key=key)
718
- result = anonymizer.anonymize_text(text)
719
-
720
- if result["success"]:
721
- stats = result.get("statistics", {})
722
- stats_md = f"""
723
- ### ✅ پردازش موفق
724
- - 🏢 شرکت‌ها: {stats.get('company', 0)}
725
- - 👤 اشخاص: {stats.get('person', 0)}
726
- - 💰 مبالغ: {stats.get('amount', 0)}
727
- - 📊 درصدها: {stats.get('percent', 0)}
728
- """
729
- return result["anonymized_text"], stats_md
730
- else:
731
- return "", f"❌ خطا: {result.get('error', 'نامشخص')}"
732
- except Exception as e:
733
- return "", f"❌ خطا: {str(e)}"
734
-
735
- def start_batch_processing(
736
- file,
737
- text_col,
738
- output_col,
739
- delay,
740
- rpm,
741
- retries,
742
- api_key
743
- ):
744
- """شروع پردازش دسته‌ای"""
745
- if file is None:
746
- yield (
747
- 0,
748
- "### ❌ خطا\nفایل انتخاب نشده است",
749
- "",
750
- "",
751
- None,
752
- gr.update(visible=False)
753
- )
754
- return
755
-
756
- key = api_key if api_key else os.getenv("CEREBRAS_API_KEY")
757
- if not key:
758
- yield (
759
- 0,
760
- "### ❌ خطا\nکلید API وارد نشده است",
761
- "",
762
- "",
763
- None,
764
- gr.update(visible=False)
765
- )
766
- return
767
-
768
- # تنظیم rate limit
769
- rate_config = RateLimitConfig(
770
- requests_per_minute=int(rpm),
771
- min_delay_between_requests=float(delay),
772
- max_retries=int(retries)
773
- )
774
-
775
- # ایجاد پردازشگر
776
- processor = BatchProcessor(api_key=key, rate_limit_config=rate_config)
777
- batch_processor["instance"] = processor
778
-
779
- log_lines = []
780
- preview_data = []
781
-
782
- # پردازش
783
- for update in processor.process_csv(file.name, text_col, output_col):
784
- update_type = update.get("type")
785
-
786
- if update_type == "error":
787
- log_lines.append(f"❌ {update['message']}")
788
- yield (
789
- 0,
790
- f"### ❌ خطا\n{update['message']}",
791
- "",
792
- "\n".join(log_lines),
793
- None,
794
- gr.update(visible=False)
795
- )
796
- return
797
-
798
- elif update_type == "info":
799
- log_lines.append(f"ℹ️ {update['message']}")
800
-
801
- elif update_type == "progress":
802
- progress = update["progress"]
803
- current = update["current"]
804
- total = update["total"]
805
- processed = update["processed"]
806
- failed = update["failed"]
807
- elapsed = update["elapsed"]
808
- remaining = update["estimated_remaining"]
809
- next_wait = update.get("next_wait", 0)
810
-
811
- progress_md = f"""
812
- ### 📈 وضعیت پردازش
813
- - **پردازش شده:** {current}/{total} ({progress:.1f}%)
814
- - **موفق:** {processed} ✅
815
- - **ناموفق:** {failed} ❌
816
- - **تأخیر بعدی:** {next_wait:.1f} ثانیه
817
- """
818
-
819
- time_md = f"""
820
- ### ⏱️ زمان‌بندی
821
- - **سپری شده:** {elapsed/60:.1f} دقیقه
822
- - **تخمین باقیمانده:** {remaining/60:.1f} دقیقه
823
- - **سرعت:** {current/elapsed*60:.1f} ردیف/دقیقه
824
- """
825
-
826
- # بروزرسانی لاگ هر 10 ردیف
827
- if current % 10 == 0 or current == total:
828
- log_lines.append(f"📊 پردازش {current}/{total} - موفق: {processed}, ناموفق: {failed}")
829
-
830
- # بروزرسانی پیش‌نمایش
831
- last_result = update.get("last_result", {})
832
- if last_result.get("success"):
833
- preview_data.append([
834
- "...", # متن اصلی خلاصه
835
- last_result.get("anonymized_text", "")[:100] + "...",
836
- "✅ موفق"
837
- ])
838
- if len(preview_data) > 5:
839
- preview_data = preview_data[-5:]
840
-
841
- yield (
842
- progress,
843
- progress_md,
844
- time_md,
845
- "\n".join(log_lines[-20:]), # فقط 20 خط آخر
846
- preview_data if preview_data else None,
847
- gr.update(visible=False)
848
- )
849
-
850
- elif update_type == "cancelled":
851
- log_lines.append(f"⏹️ {update['message']}")
852
- yield (
853
- 0,
854
- f"### ⏹️ لغو شد\nپردازش شده: {update['processed']}, ناموفق: {update['failed']}",
855
- "",
856
- "\n".join(log_lines),
857
- preview_data if preview_data else None,
858
- gr.update(visible=False)
859
- )
860
- return
861
-
862
- elif update_type == "complete":
863
- total_time = update["total_time"]
864
- log_lines.append(f"✅ {update['message']}")
865
- log_lines.append(f"📁 فایل خروجی: {update['output_path']}")
866
-
867
- progress_md = f"""
868
- ### ✅ پردازش تکمیل شد!
869
- - **کل ردیف‌ها:** {update['total']}
870
- - **موفق:** {update['processed']} ✅
871
- - **ناموفق:** {update['failed']} ❌
872
- - **زمان کل:** {total_time/60:.1f} دقیقه
873
- """
874
-
875
- time_md = f"""
876
- ### 📊 آمار نهایی
877
- - **سرعت میانگین:** {update['total']/total_time*60:.1f} ردیف/دقیقه
878
- - **نرخ موفقیت:** {update['processed']/update['total']*100:.1f}%
879
- """
880
-
881
- yield (
882
- 100,
883
- progress_md,
884
- time_md,
885
- "\n".join(log_lines),
886
- preview_data if preview_data else None,
887
- gr.update(value=update['output_path'], visible=True)
888
- )
889
-
890
- def cancel_processing():
891
- """لغو پردازش"""
892
- if batch_processor["instance"]:
893
- batch_processor["instance"].cancel()
894
- return "⏹️ درخواست لغو ارسال شد..."
895
-
896
- # اتصال رویدادها
897
- csv_file.change(
898
- fn=update_columns,
899
- inputs=[csv_file],
900
- outputs=[text_column]
901
- )
902
-
903
- single_btn.click(
904
- fn=process_single_text,
905
- inputs=[single_input, single_api_key],
906
- outputs=[single_output, single_stats]
907
- )
908
-
909
- start_btn.click(
910
- fn=start_batch_processing,
911
- inputs=[
912
- csv_file,
913
- text_column,
914
- output_column,
915
- delay_between_requests,
916
- requests_per_minute,
917
- max_retries,
918
- batch_api_key
919
- ],
920
- outputs=[
921
- progress_bar,
922
- progress_text,
923
- time_stats,
924
- process_log,
925
- preview_table,
926
- output_file
927
- ]
928
- )
929
-
930
- cancel_btn.click(
931
- fn=cancel_processing,
932
- outputs=[process_log]
933
- )
934
-
935
- return interface
936
-
937
-
938
- # اجرای برنامه
939
- if __name__ == "__main__":
940
- interface = create_batch_interface()
941
- interface.launch(
942
- server_name="0.0.0.0",
943
- server_port=7860,
944
- share=True,
945
- show_error=True
946
- )