Download app.py from danicor/w2: direct link, hf CLI and curl.
- Browser
- Download file 39.9 kB
-
https://huggingface.co/spaces/danicor/w2/resolve/main/app.py
- Command line
-
hf download hf://spaces/danicor/w2/app.py
-
curl -L -o app.py https://huggingface.co/spaces/danicor/w2/resolve/main/app.py
39.9 kB
| 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 | |
| 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": "وضعیت کسر خودکار" | |
| } | |
| } | |
| 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)) | |
| 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)) | |
| async def get_supported_languages(): | |
| """دریافت لیست زبانهای پشتیبانی شده""" | |
| return { | |
| "success": True, | |
| "languages": LANGUAGE_MAP, | |
| "total_count": len(LANGUAGE_MAP) | |
| } | |
| 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() | |
| } | |
| 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 | |
| } | |
| 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 | |
| } | |
| 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} در حال پردازش" | |
| } | |
| 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": "درخواست پیدا نشد" | |
| } | |
| 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": "درخواست ترجمه پیدا نشد" | |
| } | |
| 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 = {} | |
| 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}") | |
| 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)) | |
| async def get_cache_stats(): | |
| """آمار کش ترجمه""" | |
| stats = translator.cache.get_stats() | |
| return { | |
| "success": True, | |
| "cache_stats": stats, | |
| "expiry_minutes": 60 | |
| } | |
| 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)}") | |
| 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() | |
| } | |
| } | |
| 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" | |
| ) |