w2 / app.py
danicor's picture
Create app.py
a620b70 verified
import os
import time
import hashlib
import threading
import asyncio
import re
from datetime import datetime, timedelta
from concurrent.futures import ThreadPoolExecutor
from typing import Dict, List, Optional, Any
import json
import uuid
from fastapi import FastAPI, HTTPException, BackgroundTasks, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from pydantic import BaseModel, Field
import torch
from transformers import M2M100ForConditionalGeneration, M2M100Tokenizer
import requests
# تنظیمات اولیه
MODEL_NAME = "facebook/m2m100_418M"
CACHE_EXPIRY = 60 * 60 # 60 دقیقه
MAX_CHUNK_SIZE = 350
MAX_WORKERS = 1
CLEANUP_INTERVAL = 300 # 5 دقیقه
translations = {}
os.environ["HF_HOME"] = "/tmp/huggingface"
os.environ["TRANSFORMERS_CACHE"] = "/tmp/huggingface"
cache_dir = "/tmp/huggingface"
os.makedirs(cache_dir, mode=0o777, exist_ok=True)
# Set WordPress notification URL from environment or default
WORDPRESS_BASE_URL = os.getenv("WORDPRESS_NOTIFICATION_URL", "https://echovizio.us.to")
# نگاشت زبان‌ها
LANGUAGE_MAP = {
"English": "en",
"Persian": "fa",
"Farsi": "fa",
"Arabic": "ar",
"Spanish": "es",
"French": "fr",
"German": "de",
"Italian": "it",
"Portuguese": "pt",
"Russian": "ru",
"Chinese": "zh",
"Japanese": "ja",
"Korean": "ko",
"Hindi": "hi",
"Turkish": "tr",
"Dutch": "nl",
"Swedish": "sv",
"Norwegian": "no",
"Danish": "da",
"Finnish": "fi",
"Polish": "pl",
"Czech": "cs",
"Hungarian": "hu",
"Romanian": "ro",
"Greek": "el",
"Hebrew": "he",
"Thai": "th",
"Vietnamese": "vi",
"Indonesian": "id",
"Malay": "ms",
"Tamil": "ta",
"Bengali": "bn",
"Urdu": "ur",
"Ukrainian": "uk",
"Bulgarian": "bg",
"Croatian": "hr",
"Serbian": "sr",
"Slovak": "sk",
"Slovenian": "sl",
"Estonian": "et",
"Latvian": "lv",
"Lithuanian": "lt",
"Maltese": "mt",
"Catalan": "ca",
"Galician": "gl",
"Basque": "eu",
"Welsh": "cy",
"Irish": "ga",
"Scottish": "gd",
"Icelandic": "is",
"Albanian": "sq",
"Macedonian": "mk",
"Bosnian": "bs",
"Montenegrin": "cnr",
"Swahili": "sw",
"Amharic": "am",
"Yoruba": "yo",
"Igbo": "ig",
"Hausa": "ha",
"Somali": "so",
"Oromo": "om",
"Tigrinya": "ti",
"Afrikaans": "af",
"Zulu": "zu",
"Xhosa": "xh",
"Sotho": "st",
"Tswana": "tn",
"Tsonga": "ts",
"Venda": "ve",
"Ndebele": "nr",
"Gujarati": "gu",
"Punjabi": "pa",
"Telugu": "te",
"Kannada": "kn",
"Malayalam": "ml",
"Marathi": "mr",
"Nepali": "ne",
"Sinhala": "si",
"Burmese": "my",
"Khmer": "km",
"Lao": "lo",
"Mongolian": "mn",
"Kazakh": "kk",
"Uzbek": "uz",
"Tajik": "tg",
"Kyrgyz": "ky",
"Turkmen": "tk",
"Azerbaijani": "az",
"Georgian": "ka",
"Armenian": "hy"
}
# مدل‌های Pydantic
class TranslationRequest(BaseModel):
text: str = Field(..., description="متن برای ترجمه")
source_lang: str = Field(..., description="زبان مبدا")
target_lang: str = Field(..., description="زبان مقصد")
auto_charge: bool = Field(default=False, description="کسر خودکار اعتبار")
class TranslationFormRequest(BaseModel):
text: str
source_lang: str
target_lang: str
class CompletionCheckRequest(BaseModel):
request_id: str
class StatusCheckRequest(BaseModel):
request_id: str
class AutoChargeStatusRequest(BaseModel):
request_id: str
# کلاس کش ترجمه
class TranslationCache:
def __init__(self, expiry_minutes: int = 60):
self.cache: Dict[str, Dict] = {}
self.expiry = expiry_minutes * 60
self.lock = threading.Lock()
def _generate_key(self, text: str, source_lang: str, target_lang: str) -> str:
content = f"{text}:{source_lang}:{target_lang}"
return hashlib.md5(content.encode()).hexdigest()
def get(self, text: str, source_lang: str, target_lang: str) -> Optional[str]:
key = self._generate_key(text, source_lang, target_lang)
with self.lock:
if key in self.cache:
entry = self.cache[key]
if time.time() - entry['timestamp'] < self.expiry:
return entry['translation']
else:
del self.cache[key]
return None
def set(self, text: str, source_lang: str, target_lang: str, translation: str):
key = self._generate_key(text, source_lang, target_lang)
with self.lock:
self.cache[key] = {
'translation': translation,
'timestamp': time.time()
}
def clear_expired(self):
current_time = time.time()
with self.lock:
expired_keys = [
key for key, entry in self.cache.items()
if current_time - entry['timestamp'] >= self.expiry
]
for key in expired_keys:
del self.cache[key]
def get_stats(self) -> Dict:
with self.lock:
return {
'cache_size': len(self.cache),
'total_entries': len(self.cache)
}
# کلاس تقسیم متن
class TextChunker:
def __init__(self, max_chunk_size: int = MAX_CHUNK_SIZE):
self.max_chunk_size = max_chunk_size
def chunk_text(self, text: str) -> List[str]:
if len(text) <= self.max_chunk_size:
return [text]
# تقسیم بر اساس پاراگراف
paragraphs = text.split('\n\n')
chunks = []
current_chunk = ""
for paragraph in paragraphs:
if len(current_chunk) + len(paragraph) <= self.max_chunk_size:
if current_chunk:
current_chunk += '\n\n' + paragraph
else:
current_chunk = paragraph
else:
if current_chunk:
chunks.append(current_chunk)
if len(paragraph) <= self.max_chunk_size:
current_chunk = paragraph
else:
# تقسیم پاراگراف طولانی
sub_chunks = self._split_long_paragraph(paragraph)
chunks.extend(sub_chunks[:-1])
current_chunk = sub_chunks[-1] if sub_chunks else ""
if current_chunk:
chunks.append(current_chunk)
return chunks
def _split_long_paragraph(self, text: str) -> List[str]:
# تقسیم بر اساس جملات
sentences = re.split(r'[.!?]+\s+', text)
chunks = []
current_chunk = ""
for sentence in sentences:
if len(current_chunk) + len(sentence) <= self.max_chunk_size:
if current_chunk:
current_chunk += '. ' + sentence
else:
current_chunk = sentence
else:
if current_chunk:
chunks.append(current_chunk)
if len(sentence) <= self.max_chunk_size:
current_chunk = sentence
else:
# تقسیم بر اساس کاما
comma_parts = sentence.split(', ')
for part in comma_parts:
if len(current_chunk) + len(part) <= self.max_chunk_size:
if current_chunk:
current_chunk += ', ' + part
else:
current_chunk = part
else:
if current_chunk:
chunks.append(current_chunk)
current_chunk = part
if current_chunk:
chunks.append(current_chunk)
return chunks
# صف ترجمه
class TranslationQueue:
def __init__(self, max_workers: int = MAX_WORKERS):
self.executor = ThreadPoolExecutor(max_workers=max_workers)
self.tasks: Dict[str, Dict] = {}
self.lock = threading.Lock()
def add_task(self, session_id: str, task_func, *args, **kwargs):
with self.lock:
future = self.executor.submit(task_func, *args, **kwargs)
self.tasks[session_id] = {
'future': future,
'start_time': time.time(),
'status': 'processing'
}
def get_task_status(self, session_id: str) -> Optional[Dict]:
with self.lock:
return self.tasks.get(session_id)
def remove_task(self, session_id: str):
with self.lock:
if session_id in self.tasks:
del self.tasks[session_id]
# کلاس اصلی مترجم
class MultilingualTranslator:
def __init__(self, cache_expiry_minutes: int = 60):
print("در حال بارگذاری مدل M2M100...")
try:
# تلاش برای بارگذاری مدل و توکنایزر
self.tokenizer = M2M100Tokenizer.from_pretrained(
MODEL_NAME,
cache_dir=cache_dir,
local_files_only=False
)
self.model = M2M100ForConditionalGeneration.from_pretrained(
MODEL_NAME,
cache_dir=cache_dir,
local_files_only=False
)
# تشخیص GPU
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model.to(self.device)
print(f"مدل روی {self.device} بارگذاری شد")
except Exception as e:
print(f"خطا در بارگذاری مدل: {str(e)}")
print("تلاش مجدد با تنظیمات مختلف...")
# تلاش با تنظیمات مختلف
try:
# پاک کردن کش موجود
import shutil
if os.path.exists(cache_dir):
shutil.rmtree(cache_dir, ignore_errors=True)
os.makedirs(cache_dir, mode=0o777, exist_ok=True)
self.tokenizer = M2M100Tokenizer.from_pretrained(MODEL_NAME)
self.model = M2M100ForConditionalGeneration.from_pretrained(MODEL_NAME)
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model.to(self.device)
print(f"مدل با تلاش مجدد روی {self.device} بارگذاری شد")
except Exception as e2:
print(f"خطای نهایی در بارگذاری مدل: {str(e2)}")
raise e2
# اجزای کمکی
self.cache = TranslationCache(cache_expiry_minutes)
self.chunker = TextChunker()
self.queue = TranslationQueue()
# ذخیره وضعیت ترجمه‌ها
self.translation_sessions: Dict[str, Dict] = {}
self.completed_translations: Dict[str, Dict] = {}
self.translation_requests: Dict[str, Dict] = {}
# آمار
self.total_requests = 0
self.lock = threading.Lock()
def _normalize_language(self, lang: str) -> str:
"""تبدیل نام زبان به کد دوحرفی"""
if lang in LANGUAGE_MAP:
return LANGUAGE_MAP[lang]
elif lang.lower() in [v.lower() for v in LANGUAGE_MAP.values()]:
return lang.lower()
else:
raise ValueError(f"زبان پشتیبانی نمی‌شود: {lang}")
def translate_chunk(self, text: str, source_lang: str, target_lang: str) -> str:
"""ترجمه یک بخش از متن"""
try:
# تنظیم زبان مبدا
self.tokenizer.src_lang = source_lang
# کدگذاری متن
encoded = self.tokenizer(text, return_tensors="pt").to(self.device)
# تولید ترجمه
generated_tokens = self.model.generate(
**encoded,
forced_bos_token_id=self.tokenizer.get_lang_id(target_lang),
max_length=512,
num_beams=5,
early_stopping=True
)
# رمزگشایی نتیجه
translation = self.tokenizer.batch_decode(generated_tokens, skip_special_tokens=True)[0]
return translation.strip()
except Exception as e:
print(f"خطا در ترجمه بخش: {str(e)}")
return text
def translate_text(self, text: str, source_lang: str, target_lang: str,
session_id: Optional[str] = None) -> Dict[str, Any]:
"""ترجمه متن کامل"""
start_time = time.time()
# تبدیل کدهای زبان
source_lang = self._normalize_language(source_lang)
target_lang = self._normalize_language(target_lang)
# بررسی کش
cached_result = self.cache.get(text, source_lang, target_lang)
if cached_result:
return {
'translated_text': cached_result,
'processing_time': 0,
'chunks_count': 1,
'from_cache': True
}
# تقسیم متن
chunks = self.chunker.chunk_text(text)
translated_chunks = []
# ذخیره وضعیت session
if session_id:
with self.lock:
self.translation_sessions[session_id] = {
'total_chunks': len(chunks),
'completed_chunks': 0,
'start_time': start_time,
'status': 'processing'
}
# ترجمه هر بخش
for i, chunk in enumerate(chunks):
translated_chunk = self.translate_chunk(chunk, source_lang, target_lang)
translated_chunks.append(translated_chunk)
# بروزرسانی پیشرفت
if session_id:
with self.lock:
if session_id in self.translation_sessions:
self.translation_sessions[session_id]['completed_chunks'] = i + 1
# ترکیب نتایج
final_translation = self.combine_translations(translated_chunks)
processing_time = time.time() - start_time
# ذخیره در کش
self.cache.set(text, source_lang, target_lang, final_translation)
# بروزرسانی وضعیت نهایی
if session_id:
with self.lock:
if session_id in self.translation_sessions:
self.translation_sessions[session_id].update({
'status': 'completed',
'end_time': time.time(),
'result': final_translation
})
# افزایش آمار
with self.lock:
self.total_requests += 1
result = {
'translated_text': final_translation,
'processing_time': processing_time,
'chunks_count': len(chunks),
'from_cache': False
}
return result
def combine_translations(self, chunks: List[str]) -> str:
"""ترکیب بخش‌های ترجمه شده"""
return ' '.join(chunks)
def get_translation_progress(self, session_id: str) -> Optional[Dict]:
"""دریافت پیشرفت ترجمه"""
with self.lock:
if session_id not in self.translation_sessions:
return None
session = self.translation_sessions[session_id]
current_time = time.time()
elapsed_time = current_time - session['start_time']
if session['status'] == 'completed':
return {
'progress': 100,
'completed_chunks': session['completed_chunks'],
'total_chunks': session['total_chunks'],
'elapsed_time': elapsed_time,
'status': 'completed',
'result': session.get('result')
}
progress = (session['completed_chunks'] / session['total_chunks']) * 100
# تخمین زمان باقی‌مانده
if session['completed_chunks'] > 0:
avg_time_per_chunk = elapsed_time / session['completed_chunks']
remaining_chunks = session['total_chunks'] - session['completed_chunks']
estimated_remaining = avg_time_per_chunk * remaining_chunks
else:
estimated_remaining = None
return {
'progress': progress,
'completed_chunks': session['completed_chunks'],
'total_chunks': session['total_chunks'],
'elapsed_time': elapsed_time,
'estimated_remaining': estimated_remaining,
'status': 'processing'
}
async def translate_text_async(self, text: str, source_lang: str, target_lang: str) -> Dict[str, Any]:
"""نسخه آسنکرون ترجمه"""
loop = asyncio.get_event_loop()
with ThreadPoolExecutor() as executor:
result = await loop.run_in_executor(
executor,
self.translate_text,
text, source_lang, target_lang
)
return result
def process_heavy_translation_background(text: str, source_lang: str, target_lang: str,
request_id: str, auto_charge: bool = False):
"""پردازش ترجمه سنگین در پس‌زمینه"""
try:
translator.translation_requests[request_id] = {
'text': text,
'source_lang': source_lang,
'target_lang': target_lang,
'start_time': time.time(),
'status': 'processing',
'auto_charge': False, # حذف auto_charge
'auto_charged': False
}
result = translator.translate_text(text, source_lang, target_lang, request_id)
translator.completed_translations[request_id] = {
'result': result,
'completed_at': time.time(),
'character_count': len(text),
'translation_length': len(result['translated_text'])
}
# حذف اطلاع‌رسانی به WordPress
print(f"ترجمه پس‌زمینه {request_id} تکمیل شد - منتظر دریافت از orchestrator")
except Exception as e:
print(f"خطا در ترجمه پس‌زمینه {request_id}: {str(e)}")
if request_id in translator.translation_requests:
translator.translation_requests[request_id]['status'] ='failed'
translator.translation_requests[request_id]['error'] = str(e)
def perform_translation_internal(text: str, source_lang: str, target_lang: str) -> Dict[str, Any]:
"""تابع کمکی برای انجام ترجمه"""
try:
result = translator.translate_text(text, source_lang, target_lang)
return result
except Exception as e:
raise HTTPException(status_code=500, detail=f"خطا در ترجمه: {str(e)}")
# ایجاد مترجم
print("در حال راه‌اندازی مترجم...")
translator = MultilingualTranslator(60)
# تابع پاکسازی
def cleanup_old_data():
"""پاکسازی داده‌های قدیمی"""
while True:
try:
current_time = time.time()
# پاکسازی کش
translator.cache.clear_expired()
# پاکسازی درخواست‌های قدیمی (بیش از 2 ساعت)
expired_requests = []
for req_id, req_data in translator.translation_requests.items():
if current_time - req_data['start_time'] > 7200: # 2 ساعت
expired_requests.append(req_id)
for req_id in expired_requests:
translator.translation_requests.pop(req_id, None)
translator.completed_translations.pop(req_id, None)
# پاکسازی session های قدیمی
expired_sessions = []
for session_id, session_data in translator.translation_sessions.items():
if current_time - session_data['start_time'] > 3600: # 1 ساعت
expired_sessions.append(session_id)
for session_id in expired_sessions:
translator.translation_sessions.pop(session_id, None)
if expired_requests or expired_sessions:
print(f"پاکسازی انجام شد: {len(expired_requests)} درخواست و {len(expired_sessions)} session حذف شد")
except Exception as e:
print(f"خطا در پاکسازی: {str(e)}")
time.sleep(CLEANUP_INTERVAL)
# راه‌اندازی FastAPI
app = FastAPI(
title="سرویس ترجمه چندزبانه M2M100",
description="API ترجمه مبتنی بر مدل M2M100 فیسبوک",
version="1.0.0"
)
# تنظیم CORS
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# شروع نخ پاکسازی
cleanup_thread = threading.Thread(target=cleanup_old_data, daemon=True)
cleanup_thread.start()
# Endpoints
@app.get("/")
async def root():
"""صفحه اصلی API"""
return {
"message": "سرویس ترجمه چندزبانه M2M100",
"model": MODEL_NAME,
"device": str(translator.device),
"features": [
"multilingual_translation",
"text_chunking",
"translation_cache",
"background_processing",
"progress_tracking",
"wordpress_integration"
],
"supported_languages": len(LANGUAGE_MAP),
"endpoints": {
"/": "صفحه اصلی",
"/api/translate": "ترجمه همزمان",
"/api/translate/form": "ترجمه از فرم",
"/api/languages": "لیست زبان‌ها",
"/api/health": "وضعیت سلامت",
"/api/progress/{session_id}": "پیگیری پیشرفت",
"/api/status/{session_id}": "وضعیت کلی",
"/api/server-status": "وضعیت سرور",
"/api/check-completion": "بررسی تکمیل",
"/api/check-translation-status": "وضعیت ترجمه",
"/api/check-auto-charge-status": "وضعیت کسر خودکار"
}
}
@app.post("/api/translate")
async def translate_text_api(request: TranslationRequest):
"""ترجمه همزمان متن"""
try:
result = perform_translation_internal(
request.text,
request.source_lang,
request.target_lang
)
return {
"success": True,
"translated_text": result['translated_text'],
"processing_time": result['processing_time'],
"chunks_count": result['chunks_count'],
"from_cache": result.get('from_cache', False),
"character_count": len(request.text),
"translation_length": len(result['translated_text'])
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.post("/api/translate/form")
async def translate_form_api(request: TranslationFormRequest):
"""ترجمه از فرم (با احتمال استفاده از کش)"""
try:
result = perform_translation_internal(
request.text,
request.source_lang,
request.target_lang
)
return {
"success": True,
"translated_text": result['translated_text'],
"processing_time": result['processing_time'],
"chunks_count": result['chunks_count'],
"from_cache": result.get('from_cache', False)
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/languages")
async def get_supported_languages():
"""دریافت لیست زبان‌های پشتیبانی شده"""
return {
"success": True,
"languages": LANGUAGE_MAP,
"total_count": len(LANGUAGE_MAP)
}
@app.get("/api/health")
async def health_check():
"""بررسی سلامت سرویس"""
cache_stats = translator.cache.get_stats()
return {
"status": "healthy",
"model": MODEL_NAME,
"device": str(translator.device),
"gpu_available": torch.cuda.is_available(),
"cache_size": cache_stats['cache_size'],
"total_requests": translator.total_requests,
"active_sessions": len(translator.translation_sessions),
"completed_translations": len(translator.completed_translations),
"version": "1.0.0",
"timestamp": datetime.now().isoformat()
}
@app.get("/api/progress/{session_id}")
async def get_translation_progress(session_id: str):
"""دریافت پیشرفت ترجمه"""
progress = translator.get_translation_progress(session_id)
if progress is None:
raise HTTPException(status_code=404, detail="Session پیدا نشد یا تکمیل شده است")
return {
"success": True,
"session_id": session_id,
**progress
}
@app.get("/api/status/{session_id}")
async def get_translation_status(session_id: str):
"""دریافت وضعیت کلی ترجمه"""
progress = translator.get_translation_progress(session_id)
if progress is None:
# بررسی در ترجمه‌های تکمیل شده
if session_id in translator.completed_translations:
completed = translator.completed_translations[session_id]
return {
"success": True,
"status": "completed",
"result": completed['result'],
"completed_at": completed['completed_at']
}
else:
raise HTTPException(status_code=404, detail="Session پیدا نشد")
return {
"success": True,
"session_id": session_id,
**progress
}
@app.get("/api/server-status")
async def get_server_status():
"""دریافت وضعیت کلی سرور"""
active_sessions = len(translator.translation_sessions)
background_tasks = len(translator.translation_requests)
completed_count = len(translator.completed_translations)
# شمارش وظایف در حال پردازش
processing_count = sum(1 for req in translator.translation_requests.values()
if req.get('status') == 'processing')
return {
"success": True,
"server_status": "running",
"active_sessions": active_sessions,
"background_tasks": background_tasks,
"processing_tasks": processing_count,
"completed_translations": completed_count,
"total_requests": translator.total_requests,
"uptime": time.time(),
"message": f"سرور فعال - {active_sessions} session فعال، {processing_count} در حال پردازش"
}
@app.post("/api/check-completion")
async def check_completion(request: CompletionCheckRequest):
"""بررسی تکمیل ترجمه با request_id"""
request_id = request.request_id
# بررسی در ترجمه‌های تکمیل شده
if request_id in translator.completed_translations:
completed = translator.completed_translations[request_id]
return {
"success": True,
"completed": True,
"completed_at": completed['completed_at'],
"processing_time": completed.get('processing_time', 0)
}
# بررسی در درخواست‌های در حال پردازش
if request_id in translator.translation_requests:
req_data = translator.translation_requests[request_id]
if req_data.get('status') == 'processing':
return {
"success": True,
"completed": False,
"status": "در حال پردازش",
"elapsed_time": time.time() - req_data['start_time']
}
elif req_data.get('status') == 'failed':
return {
"success": False,
"completed": False,
"status": "ناموفق",
"error": req_data.get('error', 'خطای ناشناخته')
}
# درخواست پیدا نشد
return {
"success": False,
"completed": False,
"status": "درخواست پیدا نشد"
}
@app.post("/api/check-translation-status")
async def check_translation_status(request: StatusCheckRequest):
"""بررسی وضعیت و نتیجه نهایی ترجمه"""
request_id = request.request_id
# بررسی در ترجمه‌های تکمیل شده
if request_id in translator.completed_translations:
completed = translator.completed_translations[request_id]
req_data = translator.translation_requests.get(request_id, {})
return {
"success": True,
"status": "completed",
"translated_text": completed['result']['translated_text'],
"processing_time": completed['result']['processing_time'],
"chunks_count": completed['result']['chunks_count'],
"character_count": completed['character_count'],
"translation_length": completed['translation_length'],
"completed_at": completed['completed_at'],
"source_lang": req_data.get('source_lang'),
"target_lang": req_data.get('target_lang')
}
# بررسی در درخواست‌های در حال پردازش
if request_id in translator.translation_requests:
req_data = translator.translation_requests[request_id]
elapsed_time = time.time() - req_data['start_time']
if req_data.get('status') == 'processing':
# تخمین پیشرفت بر اساس طول متن
text_length = len(req_data.get('text', ''))
chunks_estimate = max(1, text_length // MAX_CHUNK_SIZE)
# تخمین پیشرفت (این تخمینی است)
progress_estimate = min(90, (elapsed_time / 10) * 100) # حداکثر 90% تا زمان تکمیل
return {
"success": True,
"status": "processing",
"progress": progress_estimate,
"elapsed_time": elapsed_time,
"estimated_chunks": chunks_estimate,
"message": "در حال پردازش ترجمه..."
}
elif req_data.get('status') == 'failed':
return {
"success": False,
"status": "failed",
"error": req_data.get('error', 'خطای ناشناخته'),
"elapsed_time": elapsed_time
}
# درخواست پیدا نشد
return {
"success": False,
"status": "not_found",
"message": "درخواست ترجمه پیدا نشد"
}
@app.post("/api/check-auto-charge-status")
async def check_auto_charge_status(request: AutoChargeStatusRequest):
"""بررسی وضعیت کسر اعتبار خودکار"""
request_id = request.request_id
if request_id not in translator.translation_requests:
return {
"success": False,
"message": "درخواست پیدا نشد"
}
req_data = translator.translation_requests[request_id]
return {
"success": True,
"request_id": request_id,
"auto_charge_enabled": req_data.get('auto_charge', False),
"auto_charged": req_data.get('auto_charged', False),
"status": req_data.get('status', 'unknown')
}
# دیکشنری سراسری برای وضعیت ترجمه‌ها
translations = {}
@app.post("/api/translate/heavy")
async def heavy_translate(request: Request):
data = await request.json()
# Use the request_id from orchestrator
request_id = data.get("request_id")
if not request_id:
return {"success": False, "error": "request_id is required"}
text = data.get("text")
source_lang = data.get("source_lang")
target_lang = data.get("target_lang")
auto_charge = data.get("auto_charge", False)
# Store translation info
translations[request_id] = {
"status": "processing",
"progress": 0,
"elapsed_time": 0,
"message": "Translation in progress..."
}
# Start translation in background
asyncio.create_task(run_translation_job(request_id, text, source_lang, target_lang))
return {"success": True, "request_id": request_id, "message": "Translation started"}
async def run_translation_job(request_id, text, source_lang, target_lang):
try:
# Simulate progress updates
for i in range(1, 10):
await asyncio.sleep(5)
translations[request_id]["progress"] = i * 10
translations[request_id]["elapsed_time"] += 5
# Actual translation
result = translator.translate_text(text, source_lang, target_lang)
translated_text = result['translated_text']
translations[request_id] = {
"status": "completed",
"progress": 100,
"elapsed_time": translations[request_id]["elapsed_time"],
"message": "Translation completed successfully.",
"result": translated_text
}
# Store in completed translations
translator.completed_translations[request_id] = {
'result': result,
'completed_at': time.time(),
'character_count': len(text),
'translation_length': len(translated_text)
}
print(f"✅ Translation {request_id} completed successfully")
except Exception as e:
translations[request_id] = {
"status": "failed",
"message": f"Error: {e}"
}
print(f"❌ Translation {request_id} failed: {e}")
@app.post("/api/translate/session")
async def translate_with_session(request: TranslationRequest):
"""ترجمه با session برای پیگیری پیشرفت"""
import uuid
session_id = str(uuid.uuid4())
try:
# شروع ترجمه با session_id
result = translator.translate_text(
request.text,
request.source_lang,
request.target_lang,
session_id
)
return {
"success": True,
"session_id": session_id,
"translated_text": result['translated_text'],
"processing_time": result['processing_time'],
"chunks_count": result['chunks_count'],
"from_cache": result.get('from_cache', False)
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/api/cache/stats")
async def get_cache_stats():
"""آمار کش ترجمه"""
stats = translator.cache.get_stats()
return {
"success": True,
"cache_stats": stats,
"expiry_minutes": 60
}
@app.post("/api/cache/clear")
async def clear_cache():
"""پاک کردن کش (فقط برای مدیران)"""
try:
with translator.cache.lock:
translator.cache.cache.clear()
return {
"success": True,
"message": "کش پاک شد"
}
except Exception as e:
raise HTTPException(status_code=500, detail=f"خطا در پاک کردن کش: {str(e)}")
@app.get("/api/stats")
async def get_api_stats():
"""آمار کلی API"""
return {
"success": True,
"total_requests": translator.total_requests,
"active_sessions": len(translator.translation_sessions),
"background_tasks": len(translator.translation_requests),
"completed_translations": len(translator.completed_translations),
"cache_size": translator.cache.get_stats()['cache_size'],
"supported_languages": len(LANGUAGE_MAP),
"model_info": {
"name": MODEL_NAME,
"device": str(translator.device),
"gpu_available": torch.cuda.is_available()
}
}
@app.post("/api/webhook/wordpress")
async def wordpress_webhook(data: dict):
"""Webhook برای دریافت اطلاعات از WordPress"""
try:
# پردازش داده‌های دریافتی از WordPress
request_id = data.get('request_id')
action = data.get('action')
if action == 'translation_request':
# درخواست ترجمه از WordPress
text = data.get('text')
source_lang = data.get('source_lang')
target_lang = data.get('target_lang')
auto_charge = data.get('auto_charge', False)
if not all([text, source_lang, target_lang, request_id]):
raise HTTPException(status_code=400, detail="داده‌های ناقص")
# شروع ترجمه در پس‌زمینه
background_tasks = BackgroundTasks()
background_tasks.add_task(
process_heavy_translation_background,
text, source_lang, target_lang, request_id, auto_charge
)
return {
"success": True,
"message": "ترجمه آغاز شد",
"request_id": request_id
}
elif action == 'status_check':
# بررسی وضعیت
if request_id in translator.completed_translations:
completed = translator.completed_translations[request_id]
return {
"success": True,
"status": "completed",
"result": completed['result']
}
elif request_id in translator.translation_requests:
return {
"success": True,
"status": "processing"
}
else:
return {
"success": False,
"status": "not_found"
}
else:
raise HTTPException(status_code=400, detail="عمل نامعتبر")
except Exception as e:
raise HTTPException(status_code=500, detail=f"خطا در webhook: {str(e)}")
# تابع راه‌اندازی (برای Hugging Face Spaces)
if __name__ == "__main__":
import uvicorn
port = int(os.getenv("PORT", 7860)) # پورت پیش‌فرض Hugging Face Spaces
print(f"راه‌اندازی سرور روی پورت {port}")
print(f"مدل: {MODEL_NAME}")
print(f"دستگاه: {translator.device}")
print(f"زبان‌های پشتیبانی شده: {len(LANGUAGE_MAP)}")
uvicorn.run(
app,
host="0.0.0.0",
port=port,
log_level="info"
)