danicor commited on
Commit
0e18dad
·
verified ·
1 Parent(s): 382b36b

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +1219 -0
app.py ADDED
@@ -0,0 +1,1219 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import time
3
+ import hashlib
4
+ import threading
5
+ import asyncio
6
+ import re
7
+ from datetime import datetime, timedelta
8
+ from concurrent.futures import ThreadPoolExecutor
9
+ from typing import Dict, List, Optional, Any
10
+ import json
11
+ import uuid
12
+ from fastapi import FastAPI, HTTPException, BackgroundTasks, Request
13
+ from fastapi.middleware.cors import CORSMiddleware
14
+ from fastapi.responses import JSONResponse
15
+ from pydantic import BaseModel, Field
16
+ import torch
17
+ from transformers import M2M100ForConditionalGeneration, M2M100Tokenizer
18
+ import requests
19
+
20
+ # تنظیمات اولیه
21
+ MODEL_NAME = "facebook/m2m100_418M"
22
+ CACHE_EXPIRY = 60 * 60 # 60 دقیقه
23
+ MAX_CHUNK_SIZE = 350
24
+ MAX_WORKERS = 3
25
+ CLEANUP_INTERVAL = 300 # 5 دقیقه
26
+
27
+ translations = {}
28
+
29
+ os.environ["HF_HOME"] = "/tmp/huggingface"
30
+ os.environ["TRANSFORMERS_CACHE"] = "/tmp/huggingface"
31
+ cache_dir = "/tmp/huggingface"
32
+ os.makedirs(cache_dir, mode=0o777, exist_ok=True)
33
+
34
+ # Set WordPress notification URL from environment or default
35
+ WORDPRESS_BASE_URL = os.getenv("WORDPRESS_NOTIFICATION_URL", "https://echovizio.us.to")
36
+
37
+ # نگاشت زبان‌ها
38
+ LANGUAGE_MAP = {
39
+ "English": "en",
40
+ "Persian": "fa",
41
+ "Farsi": "fa",
42
+ "Arabic": "ar",
43
+ "Spanish": "es",
44
+ "French": "fr",
45
+ "German": "de",
46
+ "Italian": "it",
47
+ "Portuguese": "pt",
48
+ "Russian": "ru",
49
+ "Chinese": "zh",
50
+ "Japanese": "ja",
51
+ "Korean": "ko",
52
+ "Hindi": "hi",
53
+ "Turkish": "tr",
54
+ "Dutch": "nl",
55
+ "Swedish": "sv",
56
+ "Norwegian": "no",
57
+ "Danish": "da",
58
+ "Finnish": "fi",
59
+ "Polish": "pl",
60
+ "Czech": "cs",
61
+ "Hungarian": "hu",
62
+ "Romanian": "ro",
63
+ "Greek": "el",
64
+ "Hebrew": "he",
65
+ "Thai": "th",
66
+ "Vietnamese": "vi",
67
+ "Indonesian": "id",
68
+ "Malay": "ms",
69
+ "Tamil": "ta",
70
+ "Bengali": "bn",
71
+ "Urdu": "ur",
72
+ "Ukrainian": "uk",
73
+ "Bulgarian": "bg",
74
+ "Croatian": "hr",
75
+ "Serbian": "sr",
76
+ "Slovak": "sk",
77
+ "Slovenian": "sl",
78
+ "Estonian": "et",
79
+ "Latvian": "lv",
80
+ "Lithuanian": "lt",
81
+ "Maltese": "mt",
82
+ "Catalan": "ca",
83
+ "Galician": "gl",
84
+ "Basque": "eu",
85
+ "Welsh": "cy",
86
+ "Irish": "ga",
87
+ "Scottish": "gd",
88
+ "Icelandic": "is",
89
+ "Albanian": "sq",
90
+ "Macedonian": "mk",
91
+ "Bosnian": "bs",
92
+ "Montenegrin": "cnr",
93
+ "Swahili": "sw",
94
+ "Amharic": "am",
95
+ "Yoruba": "yo",
96
+ "Igbo": "ig",
97
+ "Hausa": "ha",
98
+ "Somali": "so",
99
+ "Oromo": "om",
100
+ "Tigrinya": "ti",
101
+ "Afrikaans": "af",
102
+ "Zulu": "zu",
103
+ "Xhosa": "xh",
104
+ "Sotho": "st",
105
+ "Tswana": "tn",
106
+ "Tsonga": "ts",
107
+ "Venda": "ve",
108
+ "Ndebele": "nr",
109
+ "Gujarati": "gu",
110
+ "Punjabi": "pa",
111
+ "Telugu": "te",
112
+ "Kannada": "kn",
113
+ "Malayalam": "ml",
114
+ "Marathi": "mr",
115
+ "Nepali": "ne",
116
+ "Sinhala": "si",
117
+ "Burmese": "my",
118
+ "Khmer": "km",
119
+ "Lao": "lo",
120
+ "Mongolian": "mn",
121
+ "Kazakh": "kk",
122
+ "Uzbek": "uz",
123
+ "Tajik": "tg",
124
+ "Kyrgyz": "ky",
125
+ "Turkmen": "tk",
126
+ "Azerbaijani": "az",
127
+ "Georgian": "ka",
128
+ "Armenian": "hy"
129
+ }
130
+
131
+ # مدل‌های Pydantic
132
+ class TranslationRequest(BaseModel):
133
+ text: str = Field(..., description="متن برای ترجمه")
134
+ source_lang: str = Field(..., description="زبان مبدا")
135
+ target_lang: str = Field(..., description="زبان مقصد")
136
+ auto_charge: bool = Field(default=False, description="کسر خودکار اعتبار")
137
+
138
+ class TranslationFormRequest(BaseModel):
139
+ text: str
140
+ source_lang: str
141
+ target_lang: str
142
+
143
+ class CompletionCheckRequest(BaseModel):
144
+ request_id: str
145
+
146
+ class StatusCheckRequest(BaseModel):
147
+ request_id: str
148
+
149
+ class AutoChargeStatusRequest(BaseModel):
150
+ request_id: str
151
+
152
+ # کلاس کش ترجمه
153
+ class TranslationCache:
154
+ def __init__(self, expiry_minutes: int = 60):
155
+ self.cache: Dict[str, Dict] = {}
156
+ self.expiry = expiry_minutes * 60
157
+ self.lock = threading.Lock()
158
+
159
+ def _generate_key(self, text: str, source_lang: str, target_lang: str) -> str:
160
+ content = f"{text}:{source_lang}:{target_lang}"
161
+ return hashlib.md5(content.encode()).hexdigest()
162
+
163
+ def get(self, text: str, source_lang: str, target_lang: str) -> Optional[str]:
164
+ key = self._generate_key(text, source_lang, target_lang)
165
+ with self.lock:
166
+ if key in self.cache:
167
+ entry = self.cache[key]
168
+ if time.time() - entry['timestamp'] < self.expiry:
169
+ return entry['translation']
170
+ else:
171
+ del self.cache[key]
172
+ return None
173
+
174
+ def set(self, text: str, source_lang: str, target_lang: str, translation: str):
175
+ key = self._generate_key(text, source_lang, target_lang)
176
+ with self.lock:
177
+ self.cache[key] = {
178
+ 'translation': translation,
179
+ 'timestamp': time.time()
180
+ }
181
+
182
+ def clear_expired(self):
183
+ current_time = time.time()
184
+ with self.lock:
185
+ expired_keys = [
186
+ key for key, entry in self.cache.items()
187
+ if current_time - entry['timestamp'] >= self.expiry
188
+ ]
189
+ for key in expired_keys:
190
+ del self.cache[key]
191
+
192
+ def get_stats(self) -> Dict:
193
+ with self.lock:
194
+ return {
195
+ 'cache_size': len(self.cache),
196
+ 'total_entries': len(self.cache)
197
+ }
198
+
199
+ # کلاس تقسیم متن
200
+ class TextChunker:
201
+ def __init__(self, max_chunk_size: int = MAX_CHUNK_SIZE):
202
+ self.max_chunk_size = max_chunk_size
203
+
204
+ def chunk_text(self, text: str) -> List[str]:
205
+ if len(text) <= self.max_chunk_size:
206
+ return [text]
207
+
208
+ # تقسیم بر اساس پاراگراف
209
+ paragraphs = text.split('\n\n')
210
+ chunks = []
211
+ current_chunk = ""
212
+
213
+ for paragraph in paragraphs:
214
+ if len(current_chunk) + len(paragraph) <= self.max_chunk_size:
215
+ if current_chunk:
216
+ current_chunk += '\n\n' + paragraph
217
+ else:
218
+ current_chunk = paragraph
219
+ else:
220
+ if current_chunk:
221
+ chunks.append(current_chunk)
222
+
223
+ if len(paragraph) <= self.max_chunk_size:
224
+ current_chunk = paragraph
225
+ else:
226
+ # تقسیم پاراگراف طولانی
227
+ sub_chunks = self._split_long_paragraph(paragraph)
228
+ chunks.extend(sub_chunks[:-1])
229
+ current_chunk = sub_chunks[-1] if sub_chunks else ""
230
+
231
+ if current_chunk:
232
+ chunks.append(current_chunk)
233
+
234
+ return chunks
235
+
236
+ def _split_long_paragraph(self, text: str) -> List[str]:
237
+ # تقسیم بر اساس جملات
238
+ sentences = re.split(r'[.!?]+\s+', text)
239
+ chunks = []
240
+ current_chunk = ""
241
+
242
+ for sentence in sentences:
243
+ if len(current_chunk) + len(sentence) <= self.max_chunk_size:
244
+ if current_chunk:
245
+ current_chunk += '. ' + sentence
246
+ else:
247
+ current_chunk = sentence
248
+ else:
249
+ if current_chunk:
250
+ chunks.append(current_chunk)
251
+
252
+ if len(sentence) <= self.max_chunk_size:
253
+ current_chunk = sentence
254
+ else:
255
+ # تقسیم بر اساس کاما
256
+ comma_parts = sentence.split(', ')
257
+ for part in comma_parts:
258
+ if len(current_chunk) + len(part) <= self.max_chunk_size:
259
+ if current_chunk:
260
+ current_chunk += ', ' + part
261
+ else:
262
+ current_chunk = part
263
+ else:
264
+ if current_chunk:
265
+ chunks.append(current_chunk)
266
+ current_chunk = part
267
+
268
+ if current_chunk:
269
+ chunks.append(current_chunk)
270
+
271
+ return chunks
272
+
273
+ # صف ترجمه
274
+ class TranslationQueue:
275
+ def __init__(self, max_workers: int = MAX_WORKERS):
276
+ self.executor = ThreadPoolExecutor(max_workers=max_workers)
277
+ self.tasks: Dict[str, Dict] = {}
278
+ self.lock = threading.Lock()
279
+
280
+ def add_task(self, session_id: str, task_func, *args, **kwargs):
281
+ with self.lock:
282
+ future = self.executor.submit(task_func, *args, **kwargs)
283
+ self.tasks[session_id] = {
284
+ 'future': future,
285
+ 'start_time': time.time(),
286
+ 'status': 'processing'
287
+ }
288
+
289
+ def get_task_status(self, session_id: str) -> Optional[Dict]:
290
+ with self.lock:
291
+ return self.tasks.get(session_id)
292
+
293
+ def remove_task(self, session_id: str):
294
+ with self.lock:
295
+ if session_id in self.tasks:
296
+ del self.tasks[session_id]
297
+
298
+ # کلاس اصلی مترجم
299
+ class MultilingualTranslator:
300
+ def __init__(self, cache_expiry_minutes: int = 60):
301
+ print("در حال بارگذاری مدل M2M100...")
302
+
303
+ try:
304
+ # تلاش برای بارگذاری مدل و توکنایزر
305
+ self.tokenizer = M2M100Tokenizer.from_pretrained(
306
+ MODEL_NAME,
307
+ cache_dir=cache_dir,
308
+ local_files_only=False
309
+ )
310
+
311
+ self.model = M2M100ForConditionalGeneration.from_pretrained(
312
+ MODEL_NAME,
313
+ cache_dir=cache_dir,
314
+ local_files_only=False
315
+ )
316
+
317
+ # تشخیص GPU
318
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
319
+ self.model.to(self.device)
320
+ print(f"مدل روی {self.device} بارگذاری شد")
321
+
322
+ except Exception as e:
323
+ print(f"خطا در بارگذاری مدل: {str(e)}")
324
+ print("تلاش مجدد با تنظیمات مختلف...")
325
+
326
+ # تلاش با تنظیمات مختلف
327
+ try:
328
+ # پاک کردن کش موجود
329
+ import shutil
330
+ if os.path.exists(cache_dir):
331
+ shutil.rmtree(cache_dir, ignore_errors=True)
332
+ os.makedirs(cache_dir, mode=0o777, exist_ok=True)
333
+
334
+ self.tokenizer = M2M100Tokenizer.from_pretrained(MODEL_NAME)
335
+ self.model = M2M100ForConditionalGeneration.from_pretrained(MODEL_NAME)
336
+
337
+ self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
338
+ self.model.to(self.device)
339
+ print(f"مدل با تلاش مجدد روی {self.device} بارگذاری شد")
340
+
341
+ except Exception as e2:
342
+ print(f"خطای نهایی در بارگذاری مدل: {str(e2)}")
343
+ raise e2
344
+
345
+ # اجزای کمکی
346
+ self.cache = TranslationCache(cache_expiry_minutes)
347
+ self.chunker = TextChunker()
348
+ self.queue = TranslationQueue()
349
+
350
+ # ذخیره وضعیت ترجمه‌ها
351
+ self.translation_sessions: Dict[str, Dict] = {}
352
+ self.completed_translations: Dict[str, Dict] = {}
353
+ self.translation_requests: Dict[str, Dict] = {}
354
+
355
+ # آمار
356
+ self.total_requests = 0
357
+ self.lock = threading.Lock()
358
+
359
+ def _normalize_language(self, lang: str) -> str:
360
+ """تبدیل نام زبان به کد دوحرفی"""
361
+ if lang in LANGUAGE_MAP:
362
+ return LANGUAGE_MAP[lang]
363
+ elif lang.lower() in [v.lower() for v in LANGUAGE_MAP.values()]:
364
+ return lang.lower()
365
+ else:
366
+ raise ValueError(f"زبان پشتیبانی نمی‌شود: {lang}")
367
+
368
+ def translate_chunk(self, text: str, source_lang: str, target_lang: str) -> str:
369
+ """ترجمه یک بخش از متن"""
370
+ try:
371
+ # تنظیم زبان مبدا
372
+ self.tokenizer.src_lang = source_lang
373
+
374
+ # کدگذاری متن
375
+ encoded = self.tokenizer(text, return_tensors="pt").to(self.device)
376
+
377
+ # تولید ترجمه
378
+ generated_tokens = self.model.generate(
379
+ **encoded,
380
+ forced_bos_token_id=self.tokenizer.get_lang_id(target_lang),
381
+ max_length=512,
382
+ num_beams=5,
383
+ early_stopping=True
384
+ )
385
+
386
+ # رمزگشایی نتیجه
387
+ translation = self.tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)[0]
388
+
389
+ return translation.strip()
390
+
391
+ except Exception as e:
392
+ print(f"خطا در ترجمه بخش: {str(e)}")
393
+ return text
394
+
395
+ def translate_text(self, text: str, source_lang: str, target_lang: str,
396
+ session_id: Optional[str] = None) -> Dict[str, Any]:
397
+ """ترجمه متن کامل"""
398
+ start_time = time.time()
399
+
400
+ # تبدیل کدهای زبان
401
+ source_lang = self._normalize_language(source_lang)
402
+ target_lang = self._normalize_language(target_lang)
403
+
404
+ # بررسی کش
405
+ cached_result = self.cache.get(text, source_lang, target_lang)
406
+ if cached_result:
407
+ return {
408
+ 'translated_text': cached_result,
409
+ 'processing_time': 0,
410
+ 'chunks_count': 1,
411
+ 'from_cache': True
412
+ }
413
+
414
+ # تقسیم متن
415
+ chunks = self.chunker.chunk_text(text)
416
+ translated_chunks = []
417
+
418
+ # ذخیره وضعیت session
419
+ if session_id:
420
+ with self.lock:
421
+ self.translation_sessions[session_id] = {
422
+ 'total_chunks': len(chunks),
423
+ 'completed_chunks': 0,
424
+ 'start_time': start_time,
425
+ 'status': 'processing'
426
+ }
427
+
428
+ # ترجمه هر بخش
429
+ for i, chunk in enumerate(chunks):
430
+ translated_chunk = self.translate_chunk(chunk, source_lang, target_lang)
431
+ translated_chunks.append(translated_chunk)
432
+
433
+ # بروزرسانی پیشرفت
434
+ if session_id:
435
+ with self.lock:
436
+ if session_id in self.translation_sessions:
437
+ self.translation_sessions[session_id]['completed_chunks'] = i + 1
438
+
439
+ # ترکیب نتایج
440
+ final_translation = self.combine_translations(translated_chunks)
441
+ processing_time = time.time() - start_time
442
+
443
+ # ذخیره در کش
444
+ self.cache.set(text, source_lang, target_lang, final_translation)
445
+
446
+ # بروزرسانی وضعیت نهایی
447
+ if session_id:
448
+ with self.lock:
449
+ if session_id in self.translation_sessions:
450
+ self.translation_sessions[session_id].update({
451
+ 'status': 'completed',
452
+ 'end_time': time.time(),
453
+ 'result': final_translation
454
+ })
455
+
456
+ # افزایش آمار
457
+ with self.lock:
458
+ self.total_requests += 1
459
+
460
+ result = {
461
+ 'translated_text': final_translation,
462
+ 'processing_time': processing_time,
463
+ 'chunks_count': len(chunks),
464
+ 'from_cache': False
465
+ }
466
+
467
+ return result
468
+
469
+ def combine_translations(self, chunks: List[str]) -> str:
470
+ """ترکیب بخش‌های ترجمه شده"""
471
+ return ' '.join(chunks)
472
+
473
+ def get_translation_progress(self, session_id: str) -> Optional[Dict]:
474
+ """دریافت پیشرفت ترجمه"""
475
+ with self.lock:
476
+ if session_id not in self.translation_sessions:
477
+ return None
478
+
479
+ session = self.translation_sessions[session_id]
480
+ current_time = time.time()
481
+ elapsed_time = current_time - session['start_time']
482
+
483
+ if session['status'] == 'completed':
484
+ return {
485
+ 'progress': 100,
486
+ 'completed_chunks': session['completed_chunks'],
487
+ 'total_chunks': session['total_chunks'],
488
+ 'elapsed_time': elapsed_time,
489
+ 'status': 'completed',
490
+ 'result': session.get('result')
491
+ }
492
+
493
+ progress = (session['completed_chunks'] / session['total_chunks']) * 100
494
+
495
+ # تخمین زمان باقی‌مانده
496
+ if session['completed_chunks'] > 0:
497
+ avg_time_per_chunk = elapsed_time / session['completed_chunks']
498
+ remaining_chunks = session['total_chunks'] - session['completed_chunks']
499
+ estimated_remaining = avg_time_per_chunk * remaining_chunks
500
+ else:
501
+ estimated_remaining = None
502
+
503
+ return {
504
+ 'progress': progress,
505
+ 'completed_chunks': session['completed_chunks'],
506
+ 'total_chunks': session['total_chunks'],
507
+ 'elapsed_time': elapsed_time,
508
+ 'estimated_remaining': estimated_remaining,
509
+ 'status': 'processing'
510
+ }
511
+
512
+ async def translate_text_async(self, text: str, source_lang: str, target_lang: str) -> Dict[str, Any]:
513
+ """نسخه آسنکرون ترجمه"""
514
+ loop = asyncio.get_event_loop()
515
+ with ThreadPoolExecutor() as executor:
516
+ result = await loop.run_in_executor(
517
+ executor,
518
+ self.translate_text,
519
+ text, source_lang, target_lang
520
+ )
521
+ return result
522
+
523
+ # توابع مربوط به WordPress
524
+ def notify_wordpress_completion_and_charge(request_id: str, character_count: int,
525
+ translation_length: int, source_lang: str,
526
+ target_lang: str):
527
+ """Send direct translation result to WordPress with improved error handling"""
528
+ try:
529
+ # Get translation result
530
+ if request_id not in translator.completed_translations:
531
+ print(f"❌ Translation result not found for {request_id}")
532
+ return False
533
+
534
+ translation_data = translator.completed_translations[request_id]
535
+
536
+ # Use environment variable for WordPress URL
537
+ wordpress_base_url = os.getenv("WORDPRESS_NOTIFICATION_URL", "https://echovizio.us.to")
538
+
539
+ # Send complete translation data in the notification
540
+ payload = {
541
+ 'request_id': request_id,
542
+ 'character_count': character_count,
543
+ 'translation_length': translation_length,
544
+ 'source_lang': source_lang,
545
+ 'target_lang': target_lang,
546
+ 'completed_at': datetime.now().isoformat(),
547
+ 'status': 'completed',
548
+ # Include the actual translation result
549
+ 'translated_text': translation_data['result']['translated_text'],
550
+ 'processing_time': translation_data['result']['processing_time'],
551
+ 'from_cache': translation_data['result'].get('from_cache', False),
552
+ 'chunks_count': translation_data['result'].get('chunks_count', 1)
553
+ }
554
+
555
+ headers = {
556
+ 'Content-Type': 'application/json',
557
+ 'User-Agent': 'MLT-Server/1.0',
558
+ 'Accept': 'application/json'
559
+ }
560
+
561
+ notification_url = wordpress_base_url.rstrip('/') + '/wp-json/mlt/v1/notification'
562
+
563
+ print(f"📡 Sending complete translation to: {notification_url}")
564
+ print(f"🎯 Payload size: {len(payload['translated_text'])} characters")
565
+
566
+ response = requests.post(
567
+ notification_url,
568
+ json=payload,
569
+ headers=headers,
570
+ timeout=45, # Increased timeout
571
+ verify=False
572
+ )
573
+
574
+ if response.status_code == 200:
575
+ response_data = response.json()
576
+ if response_data.get('success'):
577
+ print(f"✅ WordPress notification successful for {request_id}")
578
+ return True
579
+ else:
580
+ print(f"❌ WordPress rejected notification: {response.text}")
581
+ return False
582
+ else:
583
+ print(f"❌ WordPress notification failed with status {response.status_code}")
584
+ print(f"📄 Response: {response.text}")
585
+ return False
586
+
587
+ except requests.exceptions.Timeout:
588
+ print(f"⏰ WordPress notification timeout for {request_id}")
589
+ return False
590
+ except requests.exceptions.ConnectionError:
591
+ print(f"🔌 WordPress connection error for {request_id}")
592
+ return False
593
+ except Exception as e:
594
+ print(f"💥 Notification error for {request_id}: {str(e)}")
595
+ return False
596
+
597
+ def process_heavy_translation_background(text: str, source_lang: str, target_lang: str,
598
+ request_id: str, auto_charge: bool = False):
599
+ """پردازش ترجمه سنگین در پس‌زمینه"""
600
+ try:
601
+ # ثبت درخواست
602
+ translator.translation_requests[request_id] = {
603
+ 'text': text,
604
+ 'source_lang': source_lang,
605
+ 'target_lang': target_lang,
606
+ 'start_time': time.time(),
607
+ 'status': 'processing',
608
+ 'auto_charge': auto_charge,
609
+ 'auto_charged': False
610
+ }
611
+
612
+ # انجام ترجمه
613
+ result = translator.translate_text(text, source_lang, target_lang, request_id)
614
+
615
+ # ذخیره نتیجه
616
+ translator.completed_translations[request_id] = {
617
+ 'result': result,
618
+ 'completed_at': time.time(),
619
+ 'character_count': len(text),
620
+ 'translation_length': len(result['translated_text'])
621
+ }
622
+
623
+ # اطلاع‌رسانی به WordPress در صورت نیاز
624
+ if auto_charge:
625
+ notify_wordpress_completion_and_charge(
626
+ request_id,
627
+ len(text),
628
+ len(result['translated_text']),
629
+ source_lang,
630
+ target_lang
631
+ )
632
+
633
+ print(f"ترجمه پس‌زمینه {request_id} تکمیل شد")
634
+
635
+ except Exception as e:
636
+ print(f"خطا در ترجمه پس‌زمینه {request_id}: {str(e)}")
637
+ if request_id in translator.translation_requests:
638
+ translator.translation_requests[request_id]['status'] = 'failed'
639
+ translator.translation_requests[request_id]['error'] = str(e)
640
+
641
+ def perform_translation_internal(text: str, source_lang: str, target_lang: str) -> Dict[str, Any]:
642
+ """تابع کمکی برای انجام ترجمه"""
643
+ try:
644
+ result = translator.translate_text(text, source_lang, target_lang)
645
+ return result
646
+ except Exception as e:
647
+ raise HTTPException(status_code=500, detail=f"خطا در ترجمه: {str(e)}")
648
+
649
+ # ایجاد مترجم
650
+ print("در حال راه‌اندازی مترجم...")
651
+ translator = MultilingualTranslator(60)
652
+
653
+ # تابع پاکسازی
654
+ def cleanup_old_data():
655
+ """پاکسازی داده‌های قدیمی"""
656
+ while True:
657
+ try:
658
+ current_time = time.time()
659
+
660
+ # پاکسازی کش
661
+ translator.cache.clear_expired()
662
+
663
+ # پاکسازی درخواست‌های قدیمی (بیش از 2 ساعت)
664
+ expired_requests = []
665
+ for req_id, req_data in translator.translation_requests.items():
666
+ if current_time - req_data['start_time'] > 7200: # 2 ساعت
667
+ expired_requests.append(req_id)
668
+
669
+ for req_id in expired_requests:
670
+ translator.translation_requests.pop(req_id, None)
671
+ translator.completed_translations.pop(req_id, None)
672
+
673
+ # پاکسازی session های قدیمی
674
+ expired_sessions = []
675
+ for session_id, session_data in translator.translation_sessions.items():
676
+ if current_time - session_data['start_time'] > 3600: # 1 ساعت
677
+ expired_sessions.append(session_id)
678
+
679
+ for session_id in expired_sessions:
680
+ translator.translation_sessions.pop(session_id, None)
681
+
682
+ if expired_requests or expired_sessions:
683
+ print(f"پاکسازی انجام شد: {len(expired_requests)} درخواست و {len(expired_sessions)} session حذف شد")
684
+
685
+ except Exception as e:
686
+ print(f"خطا در پاکسازی: {str(e)}")
687
+
688
+ time.sleep(CLEANUP_INTERVAL)
689
+
690
+ # راه‌اندازی FastAPI
691
+ app = FastAPI(
692
+ title="سرویس ترجمه چندزبانه M2M100",
693
+ description="API ترجمه مبتنی بر مدل M2M100 فیسبوک",
694
+ version="1.0.0"
695
+ )
696
+
697
+ # تنظیم CORS
698
+ app.add_middleware(
699
+ CORSMiddleware,
700
+ allow_origins=["*"],
701
+ allow_credentials=True,
702
+ allow_methods=["*"],
703
+ allow_headers=["*"],
704
+ )
705
+
706
+ # شروع نخ پاکسازی
707
+ cleanup_thread = threading.Thread(target=cleanup_old_data, daemon=True)
708
+ cleanup_thread.start()
709
+
710
+ # Endpoints
711
+
712
+ @app.get("/")
713
+ async def root():
714
+ """صفحه اصلی API"""
715
+ return {
716
+ "message": "سرویس ترجمه چندزبانه M2M100",
717
+ "model": MODEL_NAME,
718
+ "device": str(translator.device),
719
+ "features": [
720
+ "multilingual_translation",
721
+ "text_chunking",
722
+ "translation_cache",
723
+ "background_processing",
724
+ "progress_tracking",
725
+ "wordpress_integration"
726
+ ],
727
+ "supported_languages": len(LANGUAGE_MAP),
728
+ "endpoints": {
729
+ "/": "صفحه اصلی",
730
+ "/api/translate": "ترجمه همزمان",
731
+ "/api/translate/form": "ترجمه از فرم",
732
+ "/api/languages": "لیست زبان‌ها",
733
+ "/api/health": "وضعیت سلامت",
734
+ "/api/progress/{session_id}": "پیگیری پیشرفت",
735
+ "/api/status/{session_id}": "وضعیت کلی",
736
+ "/api/server-status": "وضعیت سرور",
737
+ "/api/check-completion": "بررسی تکمیل",
738
+ "/api/check-translation-status": "وضعیت ترجمه",
739
+ "/api/check-auto-charge-status": "وضعیت کسر خودکار"
740
+ }
741
+ }
742
+
743
+ @app.post("/api/translate")
744
+ async def translate_text_api(request: TranslationRequest):
745
+ """ترجمه همزمان متن"""
746
+ try:
747
+ result = perform_translation_internal(
748
+ request.text,
749
+ request.source_lang,
750
+ request.target_lang
751
+ )
752
+
753
+ return {
754
+ "success": True,
755
+ "translated_text": result['translated_text'],
756
+ "processing_time": result['processing_time'],
757
+ "chunks_count": result['chunks_count'],
758
+ "from_cache": result.get('from_cache', False),
759
+ "character_count": len(request.text),
760
+ "translation_length": len(result['translated_text'])
761
+ }
762
+
763
+ except Exception as e:
764
+ raise HTTPException(status_code=500, detail=str(e))
765
+
766
+ @app.post("/api/translate/form")
767
+ async def translate_form_api(request: TranslationFormRequest):
768
+ """ترجمه از فرم (با احتمال استفاده از کش)"""
769
+ try:
770
+ result = perform_translation_internal(
771
+ request.text,
772
+ request.source_lang,
773
+ request.target_lang
774
+ )
775
+
776
+ return {
777
+ "success": True,
778
+ "translated_text": result['translated_text'],
779
+ "processing_time": result['processing_time'],
780
+ "chunks_count": result['chunks_count'],
781
+ "from_cache": result.get('from_cache', False)
782
+ }
783
+
784
+ except Exception as e:
785
+ raise HTTPException(status_code=500, detail=str(e))
786
+
787
+ @app.get("/api/languages")
788
+ async def get_supported_languages():
789
+ """دریافت لیست زبان‌های پشتیبانی شده"""
790
+ return {
791
+ "success": True,
792
+ "languages": LANGUAGE_MAP,
793
+ "total_count": len(LANGUAGE_MAP)
794
+ }
795
+
796
+ @app.get("/api/health")
797
+ async def health_check():
798
+ """بررسی سلامت سرویس"""
799
+ cache_stats = translator.cache.get_stats()
800
+
801
+ return {
802
+ "status": "healthy",
803
+ "model": MODEL_NAME,
804
+ "device": str(translator.device),
805
+ "gpu_available": torch.cuda.is_available(),
806
+ "cache_size": cache_stats['cache_size'],
807
+ "total_requests": translator.total_requests,
808
+ "active_sessions": len(translator.translation_sessions),
809
+ "completed_translations": len(translator.completed_translations),
810
+ "version": "1.0.0",
811
+ "timestamp": datetime.now().isoformat()
812
+ }
813
+
814
+ @app.get("/api/progress/{session_id}")
815
+ async def get_translation_progress(session_id: str):
816
+ """دریافت پیشرفت ترجمه"""
817
+ progress = translator.get_translation_progress(session_id)
818
+
819
+ if progress is None:
820
+ raise HTTPException(status_code=404, detail="Session پیدا نشد یا تکمیل شده است")
821
+
822
+ return {
823
+ "success": True,
824
+ "session_id": session_id,
825
+ **progress
826
+ }
827
+
828
+ @app.get("/api/status/{session_id}")
829
+ async def get_translation_status(session_id: str):
830
+ """دریافت وضعیت کلی ترجمه"""
831
+ progress = translator.get_translation_progress(session_id)
832
+
833
+ if progress is None:
834
+ # بررسی در ترجمه‌های تکمیل شده
835
+ if session_id in translator.completed_translations:
836
+ completed = translator.completed_translations[session_id]
837
+ return {
838
+ "success": True,
839
+ "status": "completed",
840
+ "result": completed['result'],
841
+ "completed_at": completed['completed_at']
842
+ }
843
+ else:
844
+ raise HTTPException(status_code=404, detail="Session پیدا نشد")
845
+
846
+ return {
847
+ "success": True,
848
+ "session_id": session_id,
849
+ **progress
850
+ }
851
+
852
+ @app.get("/api/server-status")
853
+ async def get_server_status():
854
+ """دریافت وضعیت کلی سرور"""
855
+ active_sessions = len(translator.translation_sessions)
856
+ background_tasks = len(translator.translation_requests)
857
+ completed_count = len(translator.completed_translations)
858
+
859
+ # شمارش وظایف در حال پردازش
860
+ processing_count = sum(1 for req in translator.translation_requests.values()
861
+ if req.get('status') == 'processing')
862
+
863
+ return {
864
+ "success": True,
865
+ "server_status": "running",
866
+ "active_sessions": active_sessions,
867
+ "background_tasks": background_tasks,
868
+ "processing_tasks": processing_count,
869
+ "completed_translations": completed_count,
870
+ "total_requests": translator.total_requests,
871
+ "uptime": time.time(),
872
+ "message": f"سرور فعال - {active_sessions} session فعال، {processing_count} در حال پردازش"
873
+ }
874
+
875
+ @app.post("/api/check-completion")
876
+ async def check_completion(request: CompletionCheckRequest):
877
+ """بررسی تکمیل ترجمه با request_id"""
878
+ request_id = request.request_id
879
+
880
+ # بررسی در ترجمه‌های تکمیل شده
881
+ if request_id in translator.completed_translations:
882
+ completed = translator.completed_translations[request_id]
883
+ return {
884
+ "success": True,
885
+ "completed": True,
886
+ "completed_at": completed['completed_at'],
887
+ "processing_time": completed.get('processing_time', 0)
888
+ }
889
+
890
+ # بررسی در درخواست‌های در حال پردازش
891
+ if request_id in translator.translation_requests:
892
+ req_data = translator.translation_requests[request_id]
893
+ if req_data.get('status') == 'processing':
894
+ return {
895
+ "success": True,
896
+ "completed": False,
897
+ "status": "در حال پردازش",
898
+ "elapsed_time": time.time() - req_data['start_time']
899
+ }
900
+ elif req_data.get('status') == 'failed':
901
+ return {
902
+ "success": False,
903
+ "completed": False,
904
+ "status": "ناموفق",
905
+ "error": req_data.get('error', 'خطای ناشناخته')
906
+ }
907
+
908
+ # درخواست پیدا نشد
909
+ return {
910
+ "success": False,
911
+ "completed": False,
912
+ "status": "درخواست پیدا نشد"
913
+ }
914
+
915
+ @app.post("/api/check-translation-status")
916
+ async def check_translation_status(request: StatusCheckRequest):
917
+ """بررسی وضعیت و نتیجه نهایی ترجمه"""
918
+ request_id = request.request_id
919
+
920
+ # بررسی در ترجمه‌های تکمیل شده
921
+ if request_id in translator.completed_translations:
922
+ completed = translator.completed_translations[request_id]
923
+ req_data = translator.translation_requests.get(request_id, {})
924
+
925
+ return {
926
+ "success": True,
927
+ "status": "completed",
928
+ "translated_text": completed['result']['translated_text'],
929
+ "processing_time": completed['result']['processing_time'],
930
+ "chunks_count": completed['result']['chunks_count'],
931
+ "character_count": completed['character_count'],
932
+ "translation_length": completed['translation_length'],
933
+ "completed_at": completed['completed_at'],
934
+ "source_lang": req_data.get('source_lang'),
935
+ "target_lang": req_data.get('target_lang')
936
+ }
937
+
938
+ # بررسی در درخواست‌های در حال پردازش
939
+ if request_id in translator.translation_requests:
940
+ req_data = translator.translation_requests[request_id]
941
+ elapsed_time = time.time() - req_data['start_time']
942
+
943
+ if req_data.get('status') == 'processing':
944
+ # تخمین پیشرفت بر اساس طول متن
945
+ text_length = len(req_data.get('text', ''))
946
+ chunks_estimate = max(1, text_length // MAX_CHUNK_SIZE)
947
+
948
+ # تخمین پیشرفت (این تخمینی است)
949
+ progress_estimate = min(90, (elapsed_time / 10) * 100) # حداکثر 90% تا زمان تکمیل
950
+
951
+ return {
952
+ "success": True,
953
+ "status": "processing",
954
+ "progress": progress_estimate,
955
+ "elapsed_time": elapsed_time,
956
+ "estimated_chunks": chunks_estimate,
957
+ "message": "در حال پردازش ترجمه..."
958
+ }
959
+ elif req_data.get('status') == 'failed':
960
+ return {
961
+ "success": False,
962
+ "status": "failed",
963
+ "error": req_data.get('error', 'خطای ناشناخته'),
964
+ "elapsed_time": elapsed_time
965
+ }
966
+
967
+ # درخواست پیدا نشد
968
+ return {
969
+ "success": False,
970
+ "status": "not_found",
971
+ "message": "درخواست ترجمه پیدا نشد"
972
+ }
973
+
974
+ @app.post("/api/check-auto-charge-status")
975
+ async def check_auto_charge_status(request: AutoChargeStatusRequest):
976
+ """بررسی وضعیت کسر اعتبار خودکار"""
977
+ request_id = request.request_id
978
+
979
+ if request_id not in translator.translation_requests:
980
+ return {
981
+ "success": False,
982
+ "message": "درخواست پیدا نشد"
983
+ }
984
+
985
+ req_data = translator.translation_requests[request_id]
986
+
987
+ return {
988
+ "success": True,
989
+ "request_id": request_id,
990
+ "auto_charge_enabled": req_data.get('auto_charge', False),
991
+ "auto_charged": req_data.get('auto_charged', False),
992
+ "status": req_data.get('status', 'unknown')
993
+ }
994
+
995
+
996
+ # دیکشنری سراسری برای وضعیت ترجمه‌ها
997
+ translations = {}
998
+
999
+ @app.post("/api/translate/heavy")
1000
+ async def heavy_translate(request: Request):
1001
+ data = await request.json()
1002
+
1003
+ # اگر وردپرس request_id فرستاده، همونو استفاده کن
1004
+ request_id = data.get("request_id")
1005
+ if not request_id:
1006
+ request_id = str(uuid.uuid4())
1007
+
1008
+ text = data.get("text")
1009
+ source_lang = data.get("source_lang")
1010
+ target_lang = data.get("target_lang")
1011
+ auto_charge = data.get("auto_charge", False)
1012
+ notification_url = data.get("notification_url")
1013
+
1014
+ translations[request_id] = {
1015
+ "status": "processing",
1016
+ "progress": 0,
1017
+ "elapsed_time": 0,
1018
+ "message": "Translation in progress..."
1019
+ }
1020
+
1021
+ asyncio.create_task(run_translation_job(request_id, text, source_lang, target_lang, notification_url))
1022
+
1023
+ return {"success": True, "request_id": request_id, "message": "Background translation started."}
1024
+
1025
+
1026
+ async def run_translation_job(request_id, text, source_lang, target_lang, notification_url):
1027
+ try:
1028
+ for i in range(1, 10):
1029
+ await asyncio.sleep(5)
1030
+ translations[request_id]["progress"] = i * 10
1031
+ translations[request_id]["elapsed_time"] += 5
1032
+
1033
+ result = translator.translate_text(text, source_lang, target_lang)
1034
+
1035
+ translated_text = result['translated_text']
1036
+ translations[request_id] = {
1037
+ "status": "completed",
1038
+ "progress": 100,
1039
+ "elapsed_time": translations[request_id]["elapsed_time"],
1040
+ "message": "Translation completed successfully.",
1041
+ "result": translated_text
1042
+ }
1043
+
1044
+ translator.completed_translations[request_id] = {
1045
+ 'result': result,
1046
+ 'completed_at': time.time(),
1047
+ 'character_count': len(text),
1048
+ 'translation_length': len(translated_text)
1049
+ }
1050
+
1051
+ if notification_url:
1052
+ import requests
1053
+ payload = {
1054
+ "request_id": request_id,
1055
+ "status": "completed",
1056
+ "translated_text": translated_text,
1057
+ "processing_time": result['processing_time'],
1058
+ "character_count": len(text),
1059
+ "translation_length": len(translated_text),
1060
+ "from_cache": result.get('from_cache', False),
1061
+ "auto_charge": auto_charge # Add this line
1062
+ }
1063
+ try:
1064
+ r = requests.post(notification_url, json=payload, verify=False, timeout=10)
1065
+ print(f"📡 Sent notify for {request_id}, status={r.status_code}")
1066
+ except Exception as e:
1067
+ print(f"❌ Notify failed for {request_id}: {e}")
1068
+
1069
+ except Exception as e:
1070
+ translations[request_id] = {
1071
+ "status": "failed",
1072
+ "message": f"Error: {e}"
1073
+ }
1074
+
1075
+ @app.post("/api/translate/session")
1076
+ async def translate_with_session(request: TranslationRequest):
1077
+ """ترجمه با session برای پیگیری پیشرفت"""
1078
+ import uuid
1079
+
1080
+ session_id = str(uuid.uuid4())
1081
+
1082
+ try:
1083
+ # شروع ترجمه با session_id
1084
+ result = translator.translate_text(
1085
+ request.text,
1086
+ request.source_lang,
1087
+ request.target_lang,
1088
+ session_id
1089
+ )
1090
+
1091
+ return {
1092
+ "success": True,
1093
+ "session_id": session_id,
1094
+ "translated_text": result['translated_text'],
1095
+ "processing_time": result['processing_time'],
1096
+ "chunks_count": result['chunks_count'],
1097
+ "from_cache": result.get('from_cache', False)
1098
+ }
1099
+
1100
+ except Exception as e:
1101
+ raise HTTPException(status_code=500, detail=str(e))
1102
+
1103
+ @app.get("/api/cache/stats")
1104
+ async def get_cache_stats():
1105
+ """آمار کش ترجمه"""
1106
+ stats = translator.cache.get_stats()
1107
+
1108
+ return {
1109
+ "success": True,
1110
+ "cache_stats": stats,
1111
+ "expiry_minutes": 60
1112
+ }
1113
+
1114
+ @app.post("/api/cache/clear")
1115
+ async def clear_cache():
1116
+ """پاک کردن کش (فقط برای مدیران)"""
1117
+ try:
1118
+ with translator.cache.lock:
1119
+ translator.cache.cache.clear()
1120
+
1121
+ return {
1122
+ "success": True,
1123
+ "message": "کش پاک شد"
1124
+ }
1125
+ except Exception as e:
1126
+ raise HTTPException(status_code=500, detail=f"خطا در پاک کردن کش: {str(e)}")
1127
+
1128
+ @app.get("/api/stats")
1129
+ async def get_api_stats():
1130
+ """آمار کلی API"""
1131
+ return {
1132
+ "success": True,
1133
+ "total_requests": translator.total_requests,
1134
+ "active_sessions": len(translator.translation_sessions),
1135
+ "background_tasks": len(translator.translation_requests),
1136
+ "completed_translations": len(translator.completed_translations),
1137
+ "cache_size": translator.cache.get_stats()['cache_size'],
1138
+ "supported_languages": len(LANGUAGE_MAP),
1139
+ "model_info": {
1140
+ "name": MODEL_NAME,
1141
+ "device": str(translator.device),
1142
+ "gpu_available": torch.cuda.is_available()
1143
+ }
1144
+ }
1145
+
1146
+ @app.post("/api/webhook/wordpress")
1147
+ async def wordpress_webhook(data: dict):
1148
+ """Webhook برای دریافت اطلاعات از WordPress"""
1149
+ try:
1150
+ # پردازش داده‌های دریافتی از WordPress
1151
+ request_id = data.get('request_id')
1152
+ action = data.get('action')
1153
+
1154
+ if action == 'translation_request':
1155
+ # درخواست ترجمه از WordPress
1156
+ text = data.get('text')
1157
+ source_lang = data.get('source_lang')
1158
+ target_lang = data.get('target_lang')
1159
+ auto_charge = data.get('auto_charge', False)
1160
+
1161
+ if not all([text, source_lang, target_lang, request_id]):
1162
+ raise HTTPException(status_code=400, detail="داده‌های ناقص")
1163
+
1164
+ # شروع ترجمه در پس‌زمینه
1165
+ background_tasks = BackgroundTasks()
1166
+ background_tasks.add_task(
1167
+ process_heavy_translation_background,
1168
+ text, source_lang, target_lang, request_id, auto_charge
1169
+ )
1170
+
1171
+ return {
1172
+ "success": True,
1173
+ "message": "ترجمه آغاز شد",
1174
+ "request_id": request_id
1175
+ }
1176
+
1177
+ elif action == 'status_check':
1178
+ # بررسی وضعیت
1179
+ if request_id in translator.completed_translations:
1180
+ completed = translator.completed_translations[request_id]
1181
+ return {
1182
+ "success": True,
1183
+ "status": "completed",
1184
+ "result": completed['result']
1185
+ }
1186
+ elif request_id in translator.translation_requests:
1187
+ return {
1188
+ "success": True,
1189
+ "status": "processing"
1190
+ }
1191
+ else:
1192
+ return {
1193
+ "success": False,
1194
+ "status": "not_found"
1195
+ }
1196
+
1197
+ else:
1198
+ raise HTTPException(status_code=400, detail="عمل نامعتبر")
1199
+
1200
+ except Exception as e:
1201
+ raise HTTPException(status_code=500, detail=f"خطا در webhook: {str(e)}")
1202
+
1203
+ # تابع راه‌اندازی (برای Hugging Face Spaces)
1204
+ if __name__ == "__main__":
1205
+ import uvicorn
1206
+
1207
+ port = int(os.getenv("PORT", 7860)) # پورت پیش‌فرض Hugging Face Spaces
1208
+
1209
+ print(f"راه‌اندازی سرور روی پورت {port}")
1210
+ print(f"مدل: {MODEL_NAME}")
1211
+ print(f"دستگاه: {translator.device}")
1212
+ print(f"زبان‌های پشتیبانی شده: {len(LANGUAGE_MAP)}")
1213
+
1214
+ uvicorn.run(
1215
+ app,
1216
+ host="0.0.0.0",
1217
+ port=port,
1218
+ log_level="info"
1219
+ )