Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
| 1 |
import gradio as gr
|
|
|
|
| 2 |
import tempfile
|
| 3 |
import asyncio
|
| 4 |
import traceback
|
|
@@ -6,132 +7,229 @@ import os
|
|
| 6 |
import logging
|
| 7 |
import time
|
| 8 |
import threading
|
| 9 |
-
|
|
|
|
|
|
|
| 10 |
from deep_translator import GoogleTranslator
|
| 11 |
|
| 12 |
-
# ---
|
| 13 |
-
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
|
|
|
|
| 14 |
|
| 15 |
-
# --- تابع ریاستارت خودکار ---
|
| 16 |
def auto_restart_service():
|
| 17 |
RESTART_INTERVAL_SECONDS = 24 * 60 * 60
|
|
|
|
| 18 |
time.sleep(RESTART_INTERVAL_SECONDS)
|
|
|
|
| 19 |
os._exit(1)
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
#
|
| 23 |
-
|
| 24 |
-
'انگلیسی (آمریکا)': '
|
| 25 |
-
'انگلیسی (
|
| 26 |
-
'انگلیسی (استرالیا)': '
|
| 27 |
-
'انگلیسی (کانا
|
| 28 |
-
'انگلیسی (
|
| 29 |
-
'انگلیسی (ایر
|
| 30 |
-
'انگلیسی (
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
-
|
|
|
|
| 34 |
async def translate_text_google_async(text_to_translate, target_language="en"):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
if not text_to_translate or not text_to_translate.strip():
|
| 36 |
return "خطا: متنی برای ترجمه وارد نشده است.", None
|
| 37 |
|
| 38 |
def _translate():
|
|
|
|
| 39 |
try:
|
| 40 |
-
logging.info(f"شروع ترجمه: {text_to_translate[:30]}...")
|
|
|
|
| 41 |
translated = GoogleTranslator(source='fa', target=target_language).translate(text_to_translate)
|
|
|
|
| 42 |
return translated
|
| 43 |
except Exception as e:
|
| 44 |
-
logging.error(f"خطا در ترجمه: {e}")
|
| 45 |
return None
|
| 46 |
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
if not text_to_speak or not text_to_speak.strip():
|
| 57 |
-
return "خطای TTS: متن خالی است.", None
|
| 58 |
-
|
| 59 |
-
logging.info(f"TTS: شروع تولید صدا با
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
# --- تابع اصلی Wrapper ---
|
| 78 |
-
async def translate_and_speak_async_wrapper(persian_text,
|
| 79 |
if not persian_text or not persian_text.strip():
|
| 80 |
-
return "لطفاً متن فارسی را وارد کنید.", None
|
| 81 |
|
| 82 |
-
#
|
| 83 |
-
|
|
|
|
| 84 |
if not translated_text:
|
| 85 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
|
| 87 |
-
|
| 88 |
-
is_slow = True if speed_mode == "آهسته" else False
|
| 89 |
-
tts_status, audio_path = await text_to_speech_gtts_async(translated_text, accent_key, is_slow)
|
| 90 |
|
| 91 |
if not audio_path:
|
| 92 |
-
return f"{
|
| 93 |
|
| 94 |
-
return
|
| 95 |
|
| 96 |
-
# ---
|
| 97 |
FLY_PRIMARY_COLOR_HEX = "#4F46E5"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
custom_css = f"""
|
| 99 |
-
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;700&display=swap');
|
| 100 |
-
|
| 101 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 102 |
"""
|
| 103 |
|
| 104 |
-
with gr.Blocks(css=custom_css, title="Alpha Translator") as demo:
|
| 105 |
gr.HTML(f"""
|
| 106 |
<div class="app-title-card">
|
| 107 |
-
<h1>🚀 Alpha Translator
|
| 108 |
-
<p>ترجمه
|
| 109 |
</div>
|
| 110 |
""")
|
| 111 |
|
| 112 |
-
with gr.
|
| 113 |
-
with gr.
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 124 |
fn=translate_and_speak_async_wrapper,
|
| 125 |
-
inputs=[
|
| 126 |
-
outputs=[
|
| 127 |
-
)
|
| 128 |
-
|
| 129 |
-
gr.Examples(
|
| 130 |
-
examples=[["چطور میتوانم به ایستگاه مترو بروم؟", "انگلیسی (آمریکا)", "عادی"],
|
| 131 |
-
["این غذا بسیار لذیذ به نظر میرسد.", "انگلیسی (بریتانیا)", "آهسته"]],
|
| 132 |
-
inputs=[input_text, accent_dropdown, speed_radio]
|
| 133 |
)
|
| 134 |
|
| 135 |
if __name__ == "__main__":
|
| 136 |
-
threading.Thread(target=auto_restart_service, daemon=True).start()
|
| 137 |
-
demo.launch(server_name="0.0.0.0", server_port=7860)
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
import edge_tts
|
| 3 |
import tempfile
|
| 4 |
import asyncio
|
| 5 |
import traceback
|
|
|
|
| 7 |
import logging
|
| 8 |
import time
|
| 9 |
import threading
|
| 10 |
+
import sys
|
| 11 |
+
|
| 12 |
+
# --- کتابخانه جدید برای ترجمه با گوگل ---
|
| 13 |
from deep_translator import GoogleTranslator
|
| 14 |
|
| 15 |
+
# --- START: پیکربندی لاگینگ ---
|
| 16 |
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', datefmt='%Y-%m-%d %H:%M:%S')
|
| 17 |
+
# --- END: پیکربندی لاگینگ ---
|
| 18 |
|
| 19 |
+
# --- START: تابع ریاستارت خودکار (بدون تغییر) ---
|
| 20 |
def auto_restart_service():
|
| 21 |
RESTART_INTERVAL_SECONDS = 24 * 60 * 60
|
| 22 |
+
logging.info(f"سرویس برای ریاستارت خودکار پس از {RESTART_INTERVAL_SECONDS / 3600:.0f} ساعت زمانبندی شده است.")
|
| 23 |
time.sleep(RESTART_INTERVAL_SECONDS)
|
| 24 |
+
logging.info(f"زمان ریاستارت خودکار فرا رسیده است. برنامه برای ریاستارت خارج میشود...")
|
| 25 |
os._exit(1)
|
| 26 |
+
# --- END: تابع ریاستارت خودکار ---
|
| 27 |
+
|
| 28 |
+
# --- دیکشنری صداهای انگلیسی (بدون تغییر) ---
|
| 29 |
+
language_dict_persian_keys = {
|
| 30 |
+
'انگلیسی (آمریکا) - جنی (زن)': 'en-US-JennyNeural', 'انگلیسی (آمریکا) - گای (مرد)': 'en-US-GuyNeural',
|
| 31 |
+
'انگلیسی (آمریکا) - آنا (زن، صدای کودک)': 'en-US-AnaNeural', 'انگلیسی (آمریکا) - آریا (زن)': 'en-US-AriaNeural',
|
| 32 |
+
'انگلیسی (آمریکا) - کریستوفر (مرد)': 'en-US-ChristopherNeural', 'انگلیسی (آمریکا) - اریک (مرد)': 'en-US-EricNeural',
|
| 33 |
+
'انگلیسی (آمریکا) - میشل (زن)': 'en-US-MichelleNeural', 'انگلیسی (آمریکا) - راجر (مرد)': 'en-US-RogerNeural',
|
| 34 |
+
'انگلیسی (بریتانیا) - لیبی (زن)': 'en-GB-LibbyNeural', 'انگلیسی (بریتانیا) - میزی (زن)': 'en-GB-MaisieNeural',
|
| 35 |
+
'انگلیسی (بریتانیا) - رایان (مرد)': 'en-GB-RyanNeural', 'انگلیسی (بریتانیا) - سونیا (زن)': 'en-GB-SoniaNeural',
|
| 36 |
+
'انگلیسی (بریتانیا) - توماس (مرد)': 'en-GB-ThomasNeural', 'انگلیسی (بریتانیا) - میا (زن، جدید)': 'en-GB-MiaNeural',
|
| 37 |
+
'انگلیسی (استرالیا) - ناتاشا (زن)': 'en-AU-NatashaNeural', 'انگلیسی (استرالیا) - ویلیام (مرد)': 'en-AU-WilliamNeural',
|
| 38 |
+
'انگلیسی (کانادا) - کلارا (زن)': 'en-CA-ClaraNeural', 'انگلیسی (کانادا) - لیام (مرد)': 'en-CA-LiamNeural',
|
| 39 |
+
'انگلیسی (ایرلند) - امیلی (زن)': 'en-IE-EmilyNeural', 'انگلیسی (ایرلند) - کانر (مرد)': 'en-IE-ConnorNeural',
|
| 40 |
+
'انگلیسی (هند) - نیرجا (زن)': 'en-IN-NeerjaNeural', 'انگلیسی (هند) - پرابهات (مرد)': 'en-IN-PrabhatNeural',
|
| 41 |
+
'انگلیسی (آفریقای جنوبی) - لیا (زن)': 'en-ZA-LeahNeural', 'انگلیسی (آفریقای جنوبی) - لوک (مرد)': 'en-ZA-LukeNeural',
|
| 42 |
}
|
| 43 |
|
| 44 |
+
|
| 45 |
+
# --- START: تابع جدید ترجمه با Google Translate ---
|
| 46 |
async def translate_text_google_async(text_to_translate, target_language="en"):
|
| 47 |
+
"""
|
| 48 |
+
متن را با استفاده از Google Translate (از طریق کتابخانه deep-translator) ترجمه میکند.
|
| 49 |
+
این تابع به صورت آسنکرون اجرا میشود تا برنامه اصلی را مسدود نکند.
|
| 50 |
+
"""
|
| 51 |
if not text_to_translate or not text_to_translate.strip():
|
| 52 |
return "خطا: متنی برای ترجمه وارد نشده است.", None
|
| 53 |
|
| 54 |
def _translate():
|
| 55 |
+
# این تابع همزمان (synchronous) در یک ترد جداگانه اجرا میشود
|
| 56 |
try:
|
| 57 |
+
logging.info(f"شروع ترجمه متن: '{text_to_translate[:30]}...'")
|
| 58 |
+
# ترجمه از فارسی ('fa') به زبان مقصد (پیشفرض 'en')
|
| 59 |
translated = GoogleTranslator(source='fa', target=target_language).translate(text_to_translate)
|
| 60 |
+
logging.info("ترجمه با Google Translate موفق بود.")
|
| 61 |
return translated
|
| 62 |
except Exception as e:
|
| 63 |
+
logging.error(f"خطا در حین ترجمه با Google: {e}\n{traceback.format_exc()}")
|
| 64 |
return None
|
| 65 |
|
| 66 |
+
try:
|
| 67 |
+
# اجرای تابع همزمان _translate در یک ترد جداگانه برای جلوگیری از بلاک شدن
|
| 68 |
+
translated_text = await asyncio.to_thread(_translate)
|
| 69 |
+
|
| 70 |
+
if translated_text:
|
| 71 |
+
return "ترجمه موفق", translated_text
|
| 72 |
+
else:
|
| 73 |
+
return "خطا: مشکلی در فرآیند ترجمه پیش آمد.", None
|
| 74 |
+
|
| 75 |
+
except Exception as e:
|
| 76 |
+
logging.error(f"خطای غیرمنتظره در فراخوانی ترد ترجمه: {e}\n{traceback.format_exc()}")
|
| 77 |
+
return "خطای غیرمنتظره: مشکلی در سیستم ترجمه رخ داد.", None
|
| 78 |
+
# --- END: تابع جدید ترجمه ---
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
# --- تابع تولید صدا (بدون تغییر) ---
|
| 82 |
+
async def text_to_speech_edge_async(text_to_speak, tts_voice_key, rate, volume, pitch):
|
| 83 |
+
voice_id = language_dict_persian_keys.get(tts_voice_key)
|
| 84 |
+
if not voice_id:
|
| 85 |
+
return f"خطای TTS: صدای '{tts_voice_key}' یافت نشد.", None
|
| 86 |
if not text_to_speak or not text_to_speak.strip():
|
| 87 |
+
return "خطای TTS: متن ترجمه شده برای خواندن خالی است.", None
|
| 88 |
+
|
| 89 |
+
logging.info(f"TTS: شروع تولید صدا برای '{tts_voice_key}'...")
|
| 90 |
+
tmp_path = None
|
| 91 |
+
try:
|
| 92 |
+
rate_str, volume_str, pitch_str = f"{int(rate):+g}%", f"{int(volume):+g}%", f"{int(pitch):+g}Hz"
|
| 93 |
+
communicate = edge_tts.Communicate(text_to_speak, voice_id, rate=rate_str, volume=volume_str, pitch=pitch_str)
|
| 94 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
|
| 95 |
+
tmp_path = tmp_file.name
|
| 96 |
+
await communicate.save(tmp_path)
|
| 97 |
+
logging.info(f"TTS: صدا با موفقیت در '{os.path.basename(tmp_path)}' ذخیره شد.")
|
| 98 |
+
return "TTS موفق", tmp_path
|
| 99 |
+
except Exception as e:
|
| 100 |
+
if tmp_path and os.path.exists(tmp_path):
|
| 101 |
+
try: os.remove(tmp_path)
|
| 102 |
+
except Exception as e_rem: logging.warning(f"TTS: خطای پاک کردن فایل موقت: {e_rem}")
|
| 103 |
+
error_type = type(e).__name__
|
| 104 |
+
logging.error(f"TTS: خطای نامشخص برای '{voice_id}': {error_type} - {e}")
|
| 105 |
+
return f"خطای TTS ({error_type}): مشکلی در تولید صدا پیش آمد.", None
|
| 106 |
+
|
| 107 |
+
# --- تابع اصلی Wrapper (با تغییر جزئی برای فراخوانی تابع جدید) ---
|
| 108 |
+
async def translate_and_speak_async_wrapper(persian_text, english_tts_voice_key, rate, volume, pitch):
|
| 109 |
if not persian_text or not persian_text.strip():
|
| 110 |
+
return "لطفاً متن فارسی را برای ترجمه وارد کنید.", None, None
|
| 111 |
|
| 112 |
+
# --- تغییر کلیدی: فراخوانی تابع جدید ترجمه گوگل ---
|
| 113 |
+
translation_status_msg, translated_text = await translate_text_google_async(persian_text, target_language="en")
|
| 114 |
+
|
| 115 |
if not translated_text:
|
| 116 |
+
# اگر translated_text خالی است، translation_status_msg حاوی پیام خطا است
|
| 117 |
+
return translation_status_msg, None
|
| 118 |
+
|
| 119 |
+
translated_text_output = translated_text
|
| 120 |
+
|
| 121 |
+
if english_tts_voice_key not in language_dict_persian_keys:
|
| 122 |
+
if language_dict_persian_keys:
|
| 123 |
+
english_tts_voice_key = list(language_dict_persian_keys.keys())[0]
|
| 124 |
+
else:
|
| 125 |
+
return f"{translated_text_output}\n\n(خطای TTS: هیچ صدایی موجود نیست.)", None
|
| 126 |
|
| 127 |
+
tts_status_msg, audio_path = await text_to_speech_edge_async(translated_text, english_tts_voice_key, rate, volume, pitch)
|
|
|
|
|
|
|
| 128 |
|
| 129 |
if not audio_path:
|
| 130 |
+
return f"{translated_text_output}\n\n({tts_status_msg})", None
|
| 131 |
|
| 132 |
+
return translated_text_output, audio_path
|
| 133 |
|
| 134 |
+
# --- بخش UI و Gradio (کامل و بدون تغییر) ---
|
| 135 |
FLY_PRIMARY_COLOR_HEX = "#4F46E5"
|
| 136 |
+
FLY_SECONDARY_COLOR_HEX = "#10B981"
|
| 137 |
+
FLY_ACCENT_COLOR_HEX = "#D97706"
|
| 138 |
+
FLY_TEXT_COLOR_HEX = "#1F2937"
|
| 139 |
+
FLY_SUBTLE_TEXT_HEX = "#6B7280"
|
| 140 |
+
FLY_LIGHT_BACKGROUND_HEX = "#F9FAFB"
|
| 141 |
+
FLY_WHITE_HEX = "#FFFFFF"
|
| 142 |
+
FLY_BORDER_COLOR_HEX = "#D1D5DB"
|
| 143 |
+
FLY_INPUT_BG_HEX_SIMPLE = "#F3F4F6"
|
| 144 |
+
FLY_PANEL_BG_SIMPLE = "#E0F2FE"
|
| 145 |
+
|
| 146 |
+
app_theme_outer = gr.themes.Base(font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"]).set(body_background_fill=FLY_LIGHT_BACKGROUND_HEX)
|
| 147 |
custom_css = f"""
|
| 148 |
+
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
|
| 149 |
+
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800&display=swap');
|
| 150 |
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
|
| 151 |
+
:root {{
|
| 152 |
+
--fly-primary: {FLY_PRIMARY_COLOR_HEX}; --fly-secondary: {FLY_SECONDARY_COLOR_HEX};
|
| 153 |
+
--fly-accent: {FLY_ACCENT_COLOR_HEX}; --fly-text-primary: {FLY_TEXT_COLOR_HEX};
|
| 154 |
+
--fly-text-secondary: {FLY_SUBTLE_TEXT_HEX}; --fly-bg-light: {FLY_LIGHT_BACKGROUND_HEX};
|
| 155 |
+
--fly-bg-white: {FLY_WHITE_HEX}; --fly-border-color: {FLY_BORDER_COLOR_HEX};
|
| 156 |
+
--fly-input-bg-simple: {FLY_INPUT_BG_HEX_SIMPLE}; --fly-panel-bg-simple: {FLY_PANEL_BG_SIMPLE};
|
| 157 |
+
--font-global: 'Vazirmatn', 'Inter', 'Poppins', system-ui, sans-serif;
|
| 158 |
+
--font-english: 'Poppins', 'Inter', system-ui, sans-serif;
|
| 159 |
+
--radius-xl: 1rem; --shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.1),0 8px 10px -6px rgba(0,0,0,0.1);
|
| 160 |
+
--fly-primary-rgb: 79,70,229; --fly-accent-rgb: 217,119,6;
|
| 161 |
+
}}
|
| 162 |
+
body {{font-family:var(--font-global);direction:rtl;background-color:var(--fly-bg-light);color:var(--fly-text-primary);line-height:1.7;-webkit-font-smoothing:antialiased;font-size:16px;}}
|
| 163 |
+
.gradio-container {{max-width:100% !important;width:100% !important;min-height:100vh;margin:0 auto !important;padding:0 !important;background:linear-gradient(170deg, #E0F2FE 0%, #F3E8FF 100%);display:flex;flex-direction:column;}}
|
| 164 |
+
.app-title-card {{text-align:center;padding:2.5rem 1rem;margin:0;background:linear-gradient(135deg,var(--fly-primary) 0%,var(--fly-secondary) 100%);color:var(--fly-bg-white);border-bottom-left-radius:var(--radius-xl);border-bottom-right-radius:var(--radius-xl);box-shadow:var(--shadow-xl);position:relative;overflow:hidden;}}
|
| 165 |
+
.app-title-card::before {{content:'';position:absolute;top:-50px;right:-50px;width:150px;height:150px;background:rgba(255,255,255,0.1);border-radius:9999px;opacity:0.5;transform:rotate(45deg);}}
|
| 166 |
+
.app-title-card h1 {{font-size:2.25em !important;font-weight:800 !important;margin:0 0 0.5rem 0;font-family:var(--font-english);letter-spacing:-0.5px;text-shadow:0 2px 4px rgba(0,0,0,0.1);}}
|
| 167 |
+
.app-title-card p {{font-size:1em !important;margin-top:0.25rem;font-weight:400;color:rgba(255,255,255,0.85) !important;}}
|
| 168 |
+
.app-footer-fly {{text-align:center;font-size:0.85em;color:var(--fly-text-secondary);margin-top:2.5rem;padding:1rem 0;background-color:rgba(255,255,255,0.3);backdrop-filter:blur(5px);border-top:1px solid var(--fly-border-color);}}
|
| 169 |
+
footer,.gradio-footer,.flagging-container,.flex.row.gap-2.absolute.bottom-2.right-2 {{display:none !important;visibility:hidden !important;}}
|
| 170 |
+
.main-content-area {{flex-grow:1;padding:0.75rem;width:100%;margin:0 auto;box-sizing:border-box;}}
|
| 171 |
+
.content-panel-simple {{background-color:var(--fly-bg-white);padding:1rem;border-radius:var(--radius-xl);box-shadow:var(--shadow-xl);margin-top:-2rem;position:relative;z-index:10;margin-bottom:2rem;width:100%;box-sizing:border-box;}}
|
| 172 |
+
.content-panel-simple .gr-button.lg.primary {{background:var(--fly-accent) !important;margin-top:1rem !important;padding:12px 20px !important;transition:all 0.25s ease-in-out !important;color:white !important;font-weight:600 !important;border-radius:10px !important;border:none !important;box-shadow:0 3px 8px -1px rgba(var(--fly-accent-rgb),0.3) !important;width:100% !important;}}
|
| 173 |
+
.content-panel-simple .gr-button.lg.primary:hover {{background:#B45309 !important;transform:translateY(-1px) !important;box-shadow:0 5px 10px -1px rgba(var(--fly-accent-rgb),0.4) !important;}}
|
| 174 |
+
.content-panel-simple .gr-input > label + div > textarea, .content-panel-simple .gr-dropdown > label + div > div > input, .content-panel-simple .gr-textbox > label + div > textarea {{border-radius:8px !important;border:1.5px solid var(--fly-border-color) !important;background-color:var(--fly-input-bg-simple) !important;padding:10px 12px !important;}}
|
| 175 |
+
.content-panel-simple .gr-input > label + div > textarea:focus, .content-panel-simple .gr-dropdown > label + div > div > input:focus, .content-panel-simple .gr-textbox > label + div > textarea:focus {{border-color:var(--fly-primary) !important;box-shadow:0 0 0 3px rgba(var(--fly-primary-rgb),0.12) !important;background-color:var(--fly-bg-white) !important;}}
|
| 176 |
+
.content-panel-simple .gr-textbox[label*="ترجمه شده"] > label + div > textarea {{background-color:var(--fly-panel-bg-simple) !important;border-color:#A5D5FE !important;font-family:var(--font-english);font-size:1em !important;}}
|
| 177 |
+
.content-panel-simple div[label*="تنظیمات پیشرفته"] .gr-panel {{border-radius:8px !important;border:1px solid var(--fly-border-color) !important;background-color:var(--fly-input-bg-simple) !important;}}
|
| 178 |
+
@media (min-width:768px) {{.main-content-area {{max-width:780px;}} .content-panel-simple {{padding:2rem;}} .content-panel-simple .main-content-row {{display:flex !important;gap:1.5rem !important;}} .content-panel-simple .gr-button.lg.primary {{width:auto !important;align-self:flex-start;}} }}
|
| 179 |
"""
|
| 180 |
|
| 181 |
+
with gr.Blocks(theme=app_theme_outer, css=custom_css, title="Alpha Translator") as demo:
|
| 182 |
gr.HTML(f"""
|
| 183 |
<div class="app-title-card">
|
| 184 |
+
<h1>🚀 Alpha Translator</h1>
|
| 185 |
+
<p>جادوی ترجمه و تلفظ در دستان شما</p>
|
| 186 |
</div>
|
| 187 |
""")
|
| 188 |
|
| 189 |
+
with gr.Column(elem_classes=["main-content-area"]):
|
| 190 |
+
with gr.Group(elem_classes=["content-panel-simple"]):
|
| 191 |
+
with gr.Row(elem_classes=["main-content-row"]):
|
| 192 |
+
with gr.Column(scale=3, min_width=300):
|
| 193 |
+
input_text_persian = gr.Textbox(lines=4, label="📝 متن فارسی برای ترجمه", placeholder="مثال: سلام، فردا هوا چطور است؟")
|
| 194 |
+
dropdown_label = "🗣️ انتخاب گوینده و لهجه انگلیسی"
|
| 195 |
+
current_voice_list = list(language_dict_persian_keys.keys())
|
| 196 |
+
default_english_tts_voice = current_voice_list[0] if current_voice_list else "لیست صداها خالی است"
|
| 197 |
+
language_dropdown_tts_english = gr.Dropdown(choices=current_voice_list, value=default_english_tts_voice, label=dropdown_label, interactive=bool(current_voice_list))
|
| 198 |
+
|
| 199 |
+
with gr.Accordion("⚙️ تنظیمات پیشرفته صدا", open=False):
|
| 200 |
+
with gr.Row():
|
| 201 |
+
rate_slider = gr.Slider(-100, 100, 0, step=1, label="سرعت (%)", scale=1)
|
| 202 |
+
volume_slider = gr.Slider(-100, 100, 0, step=1, label="حجم (%)", scale=1)
|
| 203 |
+
pitch_slider = gr.Slider(-50, 50, 0, step=1, label="گام (Hz)")
|
| 204 |
+
submit_button = gr.Button("🚀 ترجمه و تلفظ", variant="primary", elem_classes=["lg"])
|
| 205 |
+
|
| 206 |
+
with gr.Column(scale=2, min_width=280):
|
| 207 |
+
output_text_translated = gr.Textbox(label="📜 متن ترجمه شده (انگلیسی)", interactive=False, lines=6, placeholder="متن انگلیسی ترجمه شده یا پیامهای خطا...")
|
| 208 |
+
output_audio = gr.Audio(type="filepath", label="🎧 فایل صوتی", interactive=False, autoplay=True)
|
| 209 |
+
|
| 210 |
+
if language_dict_persian_keys:
|
| 211 |
+
gr.HTML("<hr class='custom-hr' style='margin: 1.5rem 0;'>")
|
| 212 |
+
gr.Examples(
|
| 213 |
+
examples=[
|
| 214 |
+
["قیمت این لباس چقدر است؟", default_english_tts_voice, 0, 0, 0],
|
| 215 |
+
["میتوانید آدرس را روی نقشه به من نشان دهید؟", list(language_dict_persian_keys.keys())[min(1, len(language_dict_persian_keys)-1)], 0, 0, 0],
|
| 216 |
+
["ببخشید، متوجه نشدم. امکان دارد تکرار کنید؟", list(language_dict_persian_keys.keys())[min(8, len(language_dict_persian_keys)-1)], -10, 0, 0],
|
| 217 |
+
],
|
| 218 |
+
inputs=[input_text_persian, language_dropdown_tts_english, rate_slider, volume_slider, pitch_slider],
|
| 219 |
+
outputs=[output_text_translated, output_audio],
|
| 220 |
+
fn=translate_and_speak_async_wrapper,
|
| 221 |
+
cache_examples=os.getenv("GRADIO_CACHE_EXAMPLES", "False").lower() == "true",
|
| 222 |
+
label="💡 نمونههای کاربردی"
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
gr.Markdown("<p class='app-footer-fly'>Alpha Language Learning © 2025</p>")
|
| 226 |
+
|
| 227 |
+
submit_button.click(
|
| 228 |
fn=translate_and_speak_async_wrapper,
|
| 229 |
+
inputs=[input_text_persian, language_dropdown_tts_english, rate_slider, volume_slider, pitch_slider],
|
| 230 |
+
outputs=[output_text_translated, output_audio]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
)
|
| 232 |
|
| 233 |
if __name__ == "__main__":
|
| 234 |
+
threading.Thread(target=auto_restart_service, daemon=True, name="AutoRestartThread").start()
|
| 235 |
+
demo.launch(server_name="0.0.0.0", server_port=int(os.getenv("PORT", 7860)), show_error=True)
|