Spaces:
Runtime error
Runtime error
File size: 10,293 Bytes
a50bc7d acf8bfe a50bc7d 1fd0997 129257a acf8bfe 129257a db6cecb 97596e3 a50bc7d db6cecb a50bc7d fc58506 e8b7c49 a50bc7d 5dc46ff a973a24 db6cecb a973a24 a50bc7d 97596e3 a973a24 a50bc7d 97596e3 acf8bfe db6cecb 1fd0997 a371d81 db6cecb e8b7c49 a371d81 1fd0997 db6cecb 4996216 54a9930 db6cecb 97596e3 db6cecb a50bc7d db6cecb a50bc7d db6cecb a973a24 db6cecb a973a24 db6cecb 129257a db6cecb 129257a db6cecb e8b7c49 a50bc7d 129257a db6cecb 129257a 54a9930 db6cecb 54a9930 1a58b56 db6cecb 54a9930 db6cecb 1a58b56 db6cecb a50bc7d db6cecb a50bc7d db6cecb e8b7c49 a973a24 db6cecb a973a24 acf8bfe db6cecb 56ffb39 a50bc7d e8b7c49 db6cecb 129257a db6cecb e8b7c49 db6cecb 97596e3 e8b7c49 db6cecb 54a9930 db6cecb e8b7c49 54a9930 db6cecb 54a9930 e8b7c49 db6cecb a50bc7d db6cecb a50bc7d db6cecb a50bc7d db6cecb e8b7c49 a50bc7d acf8bfe a50bc7d db6cecb a50bc7d db6cecb a50bc7d db6cecb a50bc7d db6cecb a50bc7d db6cecb a50bc7d db6cecb a50bc7d db6cecb a50bc7d db6cecb a50bc7d db6cecb a50bc7d db6cecb a50bc7d db6cecb |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
import gradio as gr
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from transformers import pipeline
import langdetect
import logging
import os
from typing import Optional, Dict
import re
from functools import lru_cache
import asyncio
# --- 1. Konfigurasi Awal (Tetap Sama) ---
# Create necessary directories
os.makedirs("./cache", exist_ok=True)
os.makedirs("./logs", exist_ok=True)
# Set environment variables for Hugging Face cache
os.environ["HF_HOME"] = "./cache"
os.environ["TRANSFORMERS_CACHE"] = "./cache"
# Environment configuration
DEVICE = -1 # Selalu CPU untuk efisiensi di banyak environment
MAX_TEXT_LENGTH = int(os.getenv("MAX_TEXT_LENGTH", "5000"))
# Configure logging
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
level=logging.INFO
)
logger = logging.getLogger(__name__)
# Map model yang didukung
MODEL_MAP = {
"th": "Helsinki-NLP/opus-mt-th-en",
"ja": "Helsinki-NLP/opus-mt-ja-en",
"zh": "Helsinki-NLP/opus-mt-zh-en",
"vi": "Helsinki-NLP/opus-mt-vi-en",
}
# Istilah yang dilindungi dari translasi
PROTECTED_TERMS = ["2030 Aspirations", "Griffith"]
# Cache untuk translator (pipeline)
translators: Dict[str, pipeline] = {}
# --- Pydantic Models (Tetap Sama) ---
class TranslationRequest(BaseModel):
text: str
source_lang_override: Optional[str] = None
class TranslationResponse(BaseModel):
translated_text: str
source_language: Optional[str] = None
# --- 2. Inisialisasi Aplikasi FastAPI ---
app = FastAPI(title="Translation Service API")
# --- 3. OPTIMASI: Prapemuatan Model saat Startup ---
@app.on_event("startup")
async def startup_event():
"""Memuat semua model translasi saat aplikasi dimulai."""
logger.info("Memulai prapemuatan model translasi...")
for lang, model_name in MODEL_MAP.items():
try:
logger.info(f"Memuat model untuk bahasa: {lang} ({model_name})")
translators[lang] = pipeline("translation", model=model_name, device=DEVICE)
logger.info(f"Model untuk {lang} berhasil dimuat.")
except Exception as e:
logger.error(f"Gagal memuat model untuk {lang}: {str(e)}")
logger.info("Semua model telah dimuat.")
def get_translator(lang: str) -> pipeline:
"""Mengambil translator yang sudah dimuat dari cache."""
translator = translators.get(lang)
if not translator:
logger.error(f"Translator untuk bahasa '{lang}' tidak ditemukan. Mungkin gagal dimuat saat startup.")
raise HTTPException(status_code=500, detail=f"Model terjemahan untuk '{lang}' tidak tersedia.")
return translator
# --- Fungsi Utility (Hampir Sama, Sedikit Perbaikan) ---
@lru_cache(maxsize=128) # Cache lebih besar jika perlu
def detect_language(text: str) -> str:
"""Deteksi bahasa dengan cache."""
try:
# Potong teks untuk deteksi yang lebih cepat jika teks sangat panjang
preview_text = text[:500]
detected_lang = langdetect.detect(preview_text)
if detected_lang.startswith('zh'):
return 'zh'
return detected_lang if detected_lang in MODEL_MAP else "en"
except Exception as e:
logger.warning(f"Deteksi bahasa gagal: {str(e)}. Mengasumsikan 'en'.")
return "en"
def protect_terms(text: str, protected_terms: list) -> tuple[str, dict]:
"""Mengganti istilah yang dilindungi dengan placeholder."""
replacements = {}
for i, term in enumerate(protected_terms):
placeholder = f"__PROTECTED_{i}__"
# Gunakan word boundary (\b) untuk memastikan hanya kata utuh yang diganti
modified_text = re.sub(r'\b' + re.escape(term) + r'\b', placeholder, text, flags=re.IGNORECASE)
# Hanya tambahkan ke replacement jika ada perubahan
if modified_text != text:
replacements[placeholder] = term
text = modified_text
return text, replacements
def restore_terms(text: str, replacements: dict) -> str:
"""Mengembalikan istilah yang dilindungi."""
for placeholder, term in replacements.items():
text = text.replace(placeholder, term)
return text
# --- 4. OPTIMASI: Fungsi Inti dan Endpoint API menjadi Full Asynchronous ---
async def perform_translation(text: str, source_lang_override: Optional[str] = None) -> TranslationResponse:
"""Fungsi inti translasi yang sepenuhnya async."""
if not text or not text.strip():
raise HTTPException(status_code=400, detail="Teks input tidak boleh kosong.")
if len(text) > MAX_TEXT_LENGTH:
raise HTTPException(
status_code=413,
detail=f"Teks terlalu panjang. Panjang maksimal yang diizinkan: {MAX_TEXT_LENGTH}."
)
try:
# Tentukan bahasa sumber
if source_lang_override and source_lang_override in MODEL_MAP:
source_lang = source_lang_override
else:
source_lang = detect_language(text)
# Jika bahasa sumber adalah Inggris, kembalikan teks asli
if source_lang == "en":
return TranslationResponse(translated_text=text, source_language=source_lang)
# Ambil translator
translator = get_translator(source_lang)
# Lindungi istilah sebelum translasi
modified_text, replacements = protect_terms(text, PROTECTED_TERMS)
# --- OPTIMASI KUNCI: Jalankan model di thread terpisah ---
# Ini mencegah pipeline yang berat memblokir event loop utama
def _translate_task():
return translator(modified_text, max_length=512, num_beams=4)
result = await asyncio.to_thread(_translate_task)
translated_text = result[0]["translation_text"]
# Kembalikan istilah yang dilindungi
final_text = restore_terms(translated_text, replacements)
return TranslationResponse(translated_text=final_text, source_language=source_lang)
except HTTPException as e:
raise e # Re-raise HTTPException agar status code-nya benar
except Exception as e:
logger.error(f"Terjadi kesalahan saat translasi: {str(e)}")
raise HTTPException(status_code=500, detail=f"Proses translasi gagal: {str(e)}")
@app.post("/translate", response_model=TranslationResponse)
async def translate_api(request: TranslationRequest):
"""Endpoint API untuk translasi."""
return await perform_translation(request.text, request.source_lang_override)
@app.get("/health")
async def health_check():
return {"status": "healthy", "loaded_models": list(translators.keys())}
# --- 5. OPTIMASI: Handler Gradio menjadi Asynchronous ---
async def translate_gradio(text: str, source_lang: str = "auto"):
"""Wrapper Gradio yang sekarang async dan lebih efisien."""
if not text or not text.strip():
return "Masukkan teks untuk diterjemahkan.", "N/A"
try:
source_lang_param = source_lang if source_lang != "auto" else None
result = await perform_translation(text, source_lang_param)
return result.translated_text, result.source_language or "Unknown"
except HTTPException as e:
return f"Error: {e.detail}", "Error"
except Exception as e:
return f"Error: {str(e)}", "Error"
# --- 6. OPTIMASI: Mount Gradio ke FastAPI ---
# Fungsi untuk membuat UI Gradio tetap sama
def create_gradio_interface():
with gr.Blocks(
title="Multi-Language Translation Service",
theme=gr.themes.Soft(),
css=".gradio-container { max-width: 1200px !important; }"
) as interface:
gr.Markdown("""
# 🌐 Multi-Language Translation Service
Terjemahkan teks dari **Thai**, **Jepang**, **Mandarin**, atau **Vietnam** ke **Inggris**.
✨ Fitur: Deteksi bahasa otomatis • Perlindungan istilah • Model Helsinki-NLP yang cepat.
""")
with gr.Row():
with gr.Column(scale=1):
text_input = gr.Textbox(label="📝 Input Text", placeholder="Enter text to translate...", lines=6, max_lines=10)
with gr.Row():
lang_dropdown = gr.Dropdown(
choices=[
("🔍 Auto-detect", "auto"), ("🇹🇭 Thai", "th"), ("🇯🇵 Japanese", "ja"),
("🇨🇳 Chinese", "zh"), ("🇻🇳 Vietnamese", "vi")
],
value="auto", label="Source Language"
)
translate_btn = gr.Button("🚀 Translate", variant="primary", size="lg")
with gr.Column(scale=1):
output_text = gr.Textbox(label="🎯 Translation Result", lines=6, max_lines=10, interactive=False)
detected_lang = gr.Textbox(label="🔍 Detected Language", interactive=False, max_lines=1)
gr.Examples(
examples=[
["สวัสดีครับ ยินดีที่ได้รู้จัก การพัฒนา 2030 Aspirations เป็นเป้าหมายสำคัญ", "th"],
["こんにちは、はじめまして。Griffith大学での研究が進んでいます。", "ja"],
["你好,很高兴认识你。我们正在为2030 Aspirations制定计划。", "zh"],
["Xin chào, rất vui được gặp bạn. Griffith là trường đại học tuyệt vời.", "vi"],
],
inputs=[text_input, lang_dropdown],
outputs=[output_text, detected_lang],
fn=translate_gradio, # Sekarang memanggil fungsi async secara langsung
cache_examples=False
)
# Event handlers sekarang bisa langsung memanggil fungsi async
translate_btn.click(fn=translate_gradio, inputs=[text_input, lang_dropdown], outputs=[output_text, detected_lang])
text_input.submit(fn=translate_gradio, inputs=[text_input, lang_dropdown], outputs=[output_text, detected_lang])
return interface
# Buat UI Gradio
gradio_app = create_gradio_interface()
# Mount Gradio app ke FastAPI di path "/"
# Ini adalah cara yang benar untuk mengintegrasikan keduanya
app = gr.mount_gradio_app(app, gradio_app, path="/")
# Untuk menjalankan:
# Simpan file ini sebagai app.py dan jalankan dengan uvicorn
# > uvicorn app:app --reload --port 7860 |