leilaghomashchi commited on
Commit
2a8ee29
·
verified ·
1 Parent(s): 835b534

Delete app.py

Browse files
Files changed (1) hide show
  1. app.py +0 -424
app.py DELETED
@@ -1,424 +0,0 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
- import requests
5
- import json
6
- import gradio as gr
7
- import logging
8
- from typing import Dict, Any, Tuple
9
- import os
10
- from dataclasses import dataclass
11
- import re
12
- from difflib import SequenceMatcher
13
-
14
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
15
- logger = logging.getLogger(__name__)
16
-
17
-
18
- @dataclass
19
- class CerebrasConfig:
20
- api_key: str
21
- base_url: str = "https://api.cerebras.ai/v1"
22
- model: str = "llama-3.3-70b"
23
- max_tokens: int = 2000
24
- temperature: float = 0.1
25
-
26
-
27
- class AdvancedCerebrasAnonymizer:
28
-
29
- def __init__(self, api_key: str = None, openai_api_key: str = None):
30
- if api_key is None:
31
- api_key = os.getenv("CEREBRAS_API_KEY")
32
- if not api_key:
33
- raise ValueError("❌ کلید API Cerebras یافت نشد")
34
-
35
- self.config = CerebrasConfig(api_key=api_key)
36
- self.openai_api_key = openai_api_key or os.getenv("OPENAI_API_KEY", "")
37
- self.system_prompt = self._create_system_prompt()
38
- self.mapping_table = {}
39
- logger.info("✅ سیستم آماده شد")
40
-
41
- def _create_system_prompt(self) -> str:
42
- return """شما یک «ناشناس‌ساز متون مالی/خبری فارسی» هستید. وظیفه‌تان جایگزینی اسامی خاص و مقادیر عددی با شناسه‌های بی‌معناست.
43
-
44
- ## **قوانین اندیس‌گذاری:**
45
- - شرکت‌ها: company-01, company-02, company-03, ...
46
- - اشخاص: person-01, person-02, person-03, ...
47
- - اعداد: amount-01, amount-02, amount-03, ...
48
- - درصدها: percent-01, percent-02, percent-03, ...
49
-
50
- ## **مهم:**
51
- - اگر "ایران‌خودرو" اول‌بار company-01 شد، در تمام متن همان باشد
52
- - هر شرکت، شخص، عدد یا درصد یکی از کدها باشد"""
53
-
54
- def anonymize_text(self, text: str) -> Dict[str, Any]:
55
- try:
56
- if not text or not text.strip():
57
- return {"success": False, "error": "متن ورودی خالی است"}
58
-
59
- logger.info(f"🔄 ناشناس‌سازی... ({len(text)} کاراکتر)")
60
-
61
- headers = {
62
- "Authorization": f"Bearer {self.config.api_key}",
63
- "Content-Type": "application/json"
64
- }
65
-
66
- payload = {
67
- "model": self.config.model,
68
- "messages": [
69
- {"role": "system", "content": self.system_prompt},
70
- {"role": "user", "content": f"لطفاً این متن را ناشناس‌سازی کنید:\n\n{text}"}
71
- ],
72
- "max_tokens": self.config.max_tokens,
73
- "temperature": self.config.temperature
74
- }
75
-
76
- response = requests.post(
77
- f"{self.config.base_url}/chat/completions",
78
- headers=headers,
79
- json=payload,
80
- timeout=60
81
- )
82
-
83
- if response.status_code != 200:
84
- return {"success": False, "error": f"خطای API: {response.status_code}"}
85
-
86
- result = response.json()
87
- anonymized_text = result["choices"][0]["message"]["content"]
88
- usage = result.get("usage", {})
89
-
90
- self._extract_mapping_advanced(text, anonymized_text)
91
-
92
- logger.info(f"✅ ناشناس‌سازی موفق ({len(self.mapping_table)} موجودیت)")
93
-
94
- return {
95
- "success": True,
96
- "anonymized_text": anonymized_text,
97
- "usage": usage
98
- }
99
-
100
- except Exception as e:
101
- logger.error(f"❌ خطا: {str(e)}")
102
- return {"success": False, "error": str(e)}
103
-
104
- def _extract_mapping_advanced(self, original: str, anonymized: str):
105
- """استخراج mapping دقیق با مقایسه متن‌ها"""
106
- self.mapping_table = {}
107
-
108
- try:
109
- # تقسیم به جملات
110
- orig_lines = re.split(r'([.!?])', original)
111
- anon_lines = re.split(r'([.!?])', anonymized)
112
-
113
- for orig_line, anon_line in zip(orig_lines, anon_lines):
114
- if not orig_line.strip():
115
- continue
116
-
117
- # تقسیم به کلمات
118
- orig_words = orig_line.split()
119
- anon_words = anon_line.split()
120
-
121
- # یافتن تطابقات
122
- i = 0
123
- j = 0
124
-
125
- while i < len(orig_words) and j < len(anon_words):
126
- orig_word = orig_words[i]
127
- anon_word = anon_words[j]
128
-
129
- # اگر کدی است
130
- if re.match(r'(company|person|amount|percent)-\d+', anon_word):
131
- # سعی کن تا 3 کلمه قبل را پیدا کن
132
- found = False
133
-
134
- for phrase_len in range(min(4, len(orig_words) - i), 0, -1):
135
- phrase = ' '.join(orig_words[i:i+phrase_len])
136
-
137
- # بررسی اینکه آیا این عبارت منطقی است
138
- if self._is_valid_entity(phrase, anon_word):
139
- if phrase not in self.mapping_table:
140
- self.mapping_table[phrase] = anon_word
141
- logger.info(f" {phrase} → {anon_word}")
142
- i += phrase_len
143
- found = True
144
- break
145
-
146
- if not found:
147
- i += 1
148
- j += 1
149
- else:
150
- i += 1
151
- j += 1
152
-
153
- except Exception as e:
154
- logger.warning(f"⚠️ خطا در mapping: {str(e)}")
155
-
156
- def _is_valid_entity(self, phrase: str, code: str) -> bool:
157
- """بررسی صحت موجودیت"""
158
- phrase_lower = phrase.lower()
159
-
160
- if code.startswith('company'):
161
- return any(word in phrase_lower for word in ['شرکت', 'بانک', 'سازمان', 'وزارت', 'گروه'])
162
- elif code.startswith('person'):
163
- return len(phrase) > 2
164
- elif code.startswith('amount'):
165
- return bool(re.search(r'[\d]+', phrase))
166
- elif code.startswith('percent'):
167
- return 'درصد' in phrase_lower or '%' in phrase
168
-
169
- return False
170
-
171
- def send_to_chatgpt(self, anonymized_text: str) -> Dict[str, Any]:
172
- try:
173
- if not anonymized_text or not anonymized_text.strip():
174
- return {"success": False, "error": "متن ناشناس‌شده خالی است"}
175
-
176
- if not self.openai_api_key:
177
- return {"success": False, "error": "کلید OpenAI تنظیم نشده"}
178
-
179
- logger.info("🤖 ارسال به ChatGPT...")
180
-
181
- headers = {
182
- "Authorization": f"Bearer {self.openai_api_key}",
183
- "Content-Type": "application/json"
184
- }
185
-
186
- data = {
187
- "model": "gpt-4o-mini",
188
- "messages": [
189
- {"role": "system", "content": "شما یک تحلیلگر مالی حرفه‌ای هستید."},
190
- {"role": "user", "content": anonymized_text}
191
- ],
192
- "max_tokens": 2000,
193
- "temperature": 0.7
194
- }
195
-
196
- response = requests.post(
197
- "https://api.openai.com/v1/chat/completions",
198
- headers=headers,
199
- json=data,
200
- timeout=30
201
- )
202
-
203
- if response.status_code == 200:
204
- result = response.json()
205
- return {"success": True, "response": result['choices'][0]['message']['content']}
206
- else:
207
- error_data = response.json() if response.content else {}
208
- error_message = error_data.get('error', {}).get('message', response.text)
209
- return {"success": False, "error": error_message}
210
-
211
- except Exception as e:
212
- logger.error(f"❌ خطا: {str(e)}")
213
- return {"success": False, "error": str(e)}
214
-
215
- def _create_reverse_mapping(self, mapping_table: Dict[str, str]) -> Dict[str, str]:
216
- """ایجاد reverse mapping: code -> original"""
217
- reverse_map = {}
218
- for original, code in mapping_table.items():
219
- reverse_map[code] = original
220
- return reverse_map
221
-
222
- def deanonymize_response(self, gpt_response: str, mapping_table: Dict[str, str]) -> str:
223
- """بازگردانی تمام موجودیت‌ها (placeholder‌ها و کد‌های ساده را پشتیبانی می‌کند)"""
224
- try:
225
- if not mapping_table:
226
- return gpt_response
227
-
228
- result = gpt_response
229
-
230
- # ایجاد reverse mapping برای تبدیل code به original
231
- reverse_map = self._create_reverse_mapping(mapping_table)
232
-
233
- # مرتب‌سازی بر اساس طول (طولانی‌ترین اول)
234
- sorted_reverse = sorted(reverse_map.items(), key=lambda x: len(x[0]), reverse=True)
235
-
236
- logger.info(f"🔄 شروع بازگردانی {len(sorted_reverse)} موجودیت")
237
-
238
- for code, original in sorted_reverse:
239
- # الگوهای مختلف برای جستجو:
240
- # 1. [code] (داخل براکت)
241
- pattern_bracket = f"[{code}]"
242
- if pattern_bracket in result:
243
- result = result.replace(pattern_bracket, original)
244
- logger.info(f" [{code}] → {original} ✓")
245
-
246
- # 2. code بدون براکت
247
- if code in result:
248
- result = result.replace(code, original)
249
- logger.info(f" {code} → {original} ✓")
250
-
251
- # جایگزینی نسخه‌های دارای درصد و هزار جدا شده
252
- result = self._replace_special_patterns(result, reverse_map)
253
-
254
- logger.info(f"✅ بازگردانی موفق ({len(mapping_table)} موجودیت)")
255
- return result
256
-
257
- except Exception as e:
258
- logger.error(f"❌ خطا در بازگردانی: {str(e)}")
259
- return gpt_response
260
-
261
- def _replace_special_patterns(self, text: str, reverse_map: Dict[str, str]) -> str:
262
- """جایگزینی الگوهای خاص (درصد-XX، amount-XX و غیره) - شامل placeholder‌های بدون براکت"""
263
- try:
264
- result = text
265
-
266
- # الگوی 1: Placeholder‌های با براکت [code]
267
- pattern_bracket = r'\[((?:company|person|amount|percent|درصد|شرکت|مبلغ)-\d+)\]'
268
-
269
- def replacer_bracket(match):
270
- code = match.group(1)
271
- # تبدیل الگوهای فارسی به انگلیسی
272
- code_normalized = code.replace('درصد', 'percent').replace('شرکت', 'company').replace('مبلغ', 'amount')
273
- return reverse_map.get(code_normalized, reverse_map.get(code, match.group(0)))
274
-
275
- result = re.sub(pattern_bracket, replacer_bracket, result)
276
- logger.info(f" الگوی 1 (با براکت): جایگزین شد")
277
-
278
- # الگوی 2: Placeholder‌های داخل پرانتز (code)
279
- pattern_paren = r'\(((?:company|person|amount|percent|درصد|شرکت|مبلغ)-\d+)\)'
280
-
281
- def replacer_paren(match):
282
- code = match.group(1)
283
- code_normalized = code.replace('درصد', 'percent').replace('شرکت', 'company').replace('مبلغ', 'amount')
284
- return f"({reverse_map.get(code_normalized, reverse_map.get(code, code))})"
285
-
286
- result = re.sub(pattern_paren, replacer_paren, result)
287
- logger.info(f" الگوی 2 (پرانتز): جایگزین شد")
288
-
289
- # الگوی 3: Placeholder‌های بدون براکت (بسیار مهم!)
290
- # جایگزینی تمام code-XX که با word boundary احاطه شده‌اند
291
- pattern_simple = r'\b((?:company|person|amount|percent|درصد|شرکت|مبلغ)-\d+)\b'
292
-
293
- def replacer_simple(match):
294
- code = match.group(1)
295
- code_normalized = code.replace('درصد', 'percent').replace('شرکت', 'company').replace('مبلغ', 'amount')
296
- replacement = reverse_map.get(code_normalized, reverse_map.get(code, None))
297
- if replacement:
298
- logger.info(f" {code} → {replacement}")
299
- return replacement
300
- return match.group(0)
301
-
302
- result = re.sub(pattern_simple, replacer_simple, result)
303
- logger.info(f" الگوی 3 (بدون براکت): جایگزین شد")
304
-
305
- return result
306
-
307
- except Exception as e:
308
- logger.warning(f"⚠️ خطا در جایگزینی الگوهای خاص: {str(e)}")
309
- return text
310
-
311
-
312
- def create_interface():
313
- try:
314
- anonymizer = AdvancedCerebrasAnonymizer()
315
- except ValueError as e:
316
- return gr.Interface(fn=lambda x: str(e), inputs="textbox", outputs="textbox", title="❌ خطا")
317
-
318
- def process_text(input_text: str) -> Tuple[str, str, str, str]:
319
- logger.info("=" * 60)
320
- logger.info("شروع پردازش")
321
- logger.info("=" * 60)
322
-
323
- if not input_text.strip():
324
- return "❌ متن ورودی خالی است", "", "", ""
325
-
326
- try:
327
- # مرحله 1: ناشناس‌سازی
328
- logger.info("1️⃣ ناشناس‌سازی...")
329
- anon_result = anonymizer.anonymize_text(input_text)
330
-
331
- if not anon_result["success"]:
332
- return f"❌ {anon_result['error']}", "", "", ""
333
-
334
- anonymized_text = anon_result["anonymized_text"]
335
- usage_info = anon_result.get("usage", {})
336
-
337
- # مرحله 2: ارسال به ChatGPT
338
- logger.info("2️⃣ ارسال به ChatGPT...")
339
- gpt_result = anonymizer.send_to_chatgpt(anonymized_text)
340
-
341
- # تعریف اولیه
342
- gpt_response = ""
343
- gpt_response_deanon = ""
344
-
345
- if not gpt_result["success"]:
346
- gpt_response = f"❌ {gpt_result['error']}"
347
- gpt_response_deanon = ""
348
- else:
349
- # پاسخ ChatGPT (ناشناس)
350
- gpt_response = gpt_result["response"]
351
-
352
- # مرحله 3: بازگردانی
353
- logger.info("3️⃣ بازگردانی...")
354
- gpt_response_deanon = anonymizer.deanonymize_response(gpt_response, anonymizer.mapping_table)
355
-
356
- # آمار
357
- stats = f"""Token: {usage_info.get('total_tokens', '?')} | Mapping: {len(anonymizer.mapping_table)}"""
358
-
359
- logger.info("=" * 60)
360
- logger.info("✅ پردازش کامل")
361
- logger.info("=" * 60)
362
-
363
- # ✅ ترتیب صحیح:
364
- # 1. آمار
365
- # 2. متن ناشناس‌شده (ورودی)
366
- # 3. پاسخ ChatGPT (ناشناس) ← باکس "🤖 نتایج ChatGPT"
367
- # 4. نتیجه نهایی (بازگردانی شده) ← باکس "✅ نتیجه نهایی"
368
- return stats, anonymized_text, gpt_response, gpt_response_deanon
369
-
370
- except Exception as e:
371
- return f"❌ خطا: {str(e)}", "", "", ""
372
-
373
- def copy_text(text: str):
374
- if not text or not text.strip():
375
- return gr.update(visible=False), "⚠️ متنی وجود ندارد"
376
- return gr.update(value=text, visible=True), "✅ آماده برای کپی"
377
-
378
- def clear_all():
379
- anonymizer.mapping_table = {}
380
- return "", "", "", "", gr.update(visible=False)
381
-
382
- with gr.Blocks(title="سیستم ناشناس‌سازی", theme=gr.themes.Soft()) as interface:
383
- gr.HTML("<h1 style='text-align: center; color: #FFD700;'>🔐 سیستم ناشناس‌سازی</h1>")
384
-
385
- with gr.Row():
386
- # ستون 1
387
- with gr.Column(scale=1):
388
- gr.HTML("<h2>📥 ورودی</h2>")
389
- input_text = gr.Textbox(lines=20, placeholder="متن را وارد کنید...", label="", rtl=True)
390
-
391
- process_btn = gr.Button("🚀 پردازش", variant="primary", size="lg")
392
- with gr.Row():
393
- copy_btn = gr.Button("📋 کپی", scale=1)
394
- clear_btn = gr.Button("🗑️ پاک", variant="stop", scale=1)
395
-
396
- copy_output = gr.Textbox(visible=False)
397
-
398
- # ستون 2
399
- with gr.Column(scale=1):
400
- gr.HTML("<h2>🎭 متن ناشناس‌شده</h2>")
401
- anonymized_output = gr.Textbox(lines=20, placeholder="", label="", interactive=False, rtl=True)
402
-
403
- # ستون 3
404
- with gr.Column(scale=1):
405
- gr.HTML("<h2>🤖 نتایج ChatGPT</h2>")
406
- gpt_output = gr.Textbox(lines=10, placeholder="", label="📤 پاسخ", interactive=False, rtl=True)
407
- final_output = gr.Textbox(lines=10, placeholder="", label="✅ نتیجه نهایی", interactive=False, rtl=True)
408
- statistics_output = gr.Textbox(lines=1, label="📊 آمار", interactive=False)
409
-
410
- process_btn.click(fn=process_text, inputs=[input_text], outputs=[statistics_output, anonymized_output, gpt_output, final_output])
411
- copy_btn.click(fn=copy_text, inputs=[final_output], outputs=[copy_output, statistics_output])
412
- clear_btn.click(fn=clear_all, outputs=[input_text, anonymized_output, gpt_output, final_output, copy_output])
413
-
414
- return interface
415
-
416
-
417
- def main():
418
- print("\n🔐 سیستم ناشناس‌سازی - نسخه 1.0.0\n")
419
- interface = create_interface()
420
- interface.launch(server_name="0.0.0.0", server_port=7860, share=False, show_error=True)
421
-
422
-
423
- if __name__ == "__main__":
424
- main()