Hamed744 commited on
Commit
0a3d1bd
·
verified ·
1 Parent(s): 8a69473

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +62 -49
app.py CHANGED
@@ -23,13 +23,14 @@ if GOOGLE_API_KEY:
23
  else:
24
  logging.warning("هشدار: کلید API گوگل (GOOGLE_API_KEY) تنظیم نشده است. قابلیت ترجمه غیرفعال خواهد بود.")
25
 
 
 
26
  language_dict_persian_keys = {
27
  'انگلیسی (آمریکا) - جنی (زن)': 'en-US-JennyNeural', 'انگلیسی (آمریکا) - گای (مرد)': 'en-US-GuyNeural',
28
  'انگلیسی (آمریکا) - آنا (زن، صدای کودک)': 'en-US-AnaNeural', 'انگلیسی (آمریکا) - آریا (زن)': 'en-US-AriaNeural',
29
  'انگلیسی (آمریکا) - کریستوفر (مرد)': 'en-US-ChristopherNeural', 'انگلیسی (آمریکا) - اریک (مرد)': 'en-US-EricNeural',
30
  'انگلیسی (آمریکا) - میشل (زن)': 'en-US-MichelleNeural', 'انگلیسی (آمریکا) - راجر (مرد)': 'en-US-RogerNeural',
31
- # 'انگلیسی (آمریکا) - استفانی (زن)': 'en-US-SteffanNeural', # ممکن است منسوخ شده باشد
32
- 'انگلیسی (آمریکا) - جیسون (مرد)': 'en-US-JasonNeural',
33
  'انگلیسی (آمریکا) - براندون (مرد)': 'en-US-BrandonNeural', 'انگلیسی (آمریکا) - الیزابت (زن)': 'en-US-ElizabethNeural',
34
  'انگلیسی (آمریکا) - دیویس (مرد، جدید)': 'en-US-DavisNeural', 'انگلیسی (آمریکا) - جین (زن، جدید)': 'en-US-JaneNeural',
35
  'انگلیسی (آمریکا) - نانسی (زن، جدید)': 'en-US-NancyNeural', 'انگلیسی (آمریکا) - سارا (زن، جدید)': 'en-US-SaraNeural',
@@ -44,11 +45,10 @@ language_dict_persian_keys = {
44
  'انگلیسی (استرالیا) - کن (مرد، جدید)': 'en-AU-KenNeural', 'انگلیسی (استرالیا) - کیمبرلی (زن، جدید)': 'en-AU-KimNeural',
45
  'انگلیسی (استرالیا) - تینا (زن، جدید)': 'en-AU-TinaNeural',
46
  'انگلیسی (کانادا) - کلارا (زن)': 'en-CA-ClaraNeural', 'انگلیسی (کانادا) - لیام (مرد)': 'en-CA-LiamNeural',
47
- # 'انگلیسی (کانادا) - کوبی (مرد، جدید)': 'en-CA-CobiNeural', # بررسی شود
48
- 'انگلیسی (کانادا) - ژان (مرد، جدید)': 'en-CA-JeanNeural',
49
  'انگلیسی (ایرلند) - امیلی (زن)': 'en-IE-EmilyNeural', 'انگلیسی (ایرلند) - کانر (مرد)': 'en-IE-ConnorNeural',
50
  'انگلیسی (هند) - نیرجا (زن)': 'en-IN-NeerjaNeural', 'انگلیسی (هند) - پرابهات (مرد)': 'en-IN-PrabhatNeural',
51
- # 'انگلیسی (هند) - میتالی (زن، جدید)': 'en-IN-MithaliNeural', # بررسی شود
52
  'انگلیسی (آفریقای جنوبی) - لیا (زن)': 'en-ZA-LeahNeural', 'انگلیسی (آفریقای جنوبی) - لوک (مرد)': 'en-ZA-LukeNeural',
53
  'انگلیسی (هنگ کنگ) - یان (زن)': 'en-HK-YanNeural', 'انگلیسی (هنگ کنگ) - سم (مرد)': 'en-HK-SamNeural',
54
  'انگلیسی (نیوزیلند) - هیزل (زن)': 'en-NZ-HazelNeural', 'انگلیسی (نیوزیلند) - میچل (مرد)': 'en-NZ-MitchellNeural',
@@ -57,11 +57,14 @@ language_dict_persian_keys = {
57
  'انگلیسی (کنیا) - آسیلیا (زن)': 'en-KE-AsiliaNeural', 'انگلیسی (کنیا) - چیلمبا (مرد)': 'en-KE-ChilembaNeural',
58
  'انگلیسی (نیجریه) - ازینه (زن)': 'en-NG-EzinneNeural', 'انگلیسی (نیجریه) - آبئو (مرد)': 'en-NG-AbeoNeural',
59
  'انگلیسی (تانزانیا) - ایمانی (زن)': 'en-TZ-ImaniNeural', 'انگلیسی (تانزانیا) - الیمو (مرد)': 'en-TZ-ElimuNeural',
 
 
 
60
  }
61
 
62
 
63
  async def translate_text_gemini(text, target_language="English"):
64
- if not model: return "خطا: سرویس ترجمه در دسترس نیست (کلید API یا مدل مشکل دارد).", None
65
  if not text or not text.strip(): return "خطا: متنی برای ترجمه وارد نشده است.", None
66
  try:
67
  prompt = f"Translate the following Persian text to {target_language}. Provide only the translated English text, naturally and fluently, without any extra phrases, explanations, or markdown formatting. Be concise and accurate.\n\nPersian: \"{text}\"\n{target_language}:"
@@ -74,9 +77,6 @@ async def translate_text_gemini(text, target_language="English"):
74
  logging.error(f"خطای Gemini: {e}\n{traceback.format_exc()}")
75
  return f"خطای ترجمه: {type(e).__name__}", None
76
 
77
- MAX_TTS_RETRIES = 1 # کاهش تعداد تلاش مجدد برای جلوگیری از کندی زیاد
78
- TTS_RETRY_DELAY = 0.5 # کاهش تاخیر
79
-
80
  async def text_to_speech_edge_async(text_to_speak, tts_voice_key, rate, volume, pitch):
81
  voice_id = language_dict_persian_keys.get(tts_voice_key)
82
  if voice_id is None:
@@ -87,53 +87,53 @@ async def text_to_speech_edge_async(text_to_speak, tts_voice_key, rate, volume,
87
 
88
  if not text_to_speak or not text_to_speak.strip():
89
  return "خطای TTS: متن ترجمه شده برای خواندن خالی است.", None
90
-
91
- for attempt in range(MAX_TTS_RETRIES + 1):
92
- try:
93
- rate_str, volume_str, pitch_str = f"{int(rate):+g}%", f"{int(volume):+g}%", f"{int(pitch):+g}Hz"
94
- communicate = edge_tts.Communicate(text_to_speak, voice_id, rate=rate_str, volume=volume_str, pitch=pitch_str)
95
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
96
- tmp_path = tmp_file.name
97
-
98
- await communicate.save(tmp_path)
99
- logging.info(f"صدای '{voice_id}' (تلاش {attempt + 1}) با موفقیت در '{tmp_path}' ذخیره شد.")
100
- return "TTS موفق", tmp_path
101
-
102
- except edge_tts.exceptions.NoAudioReceived as e_no_audio:
103
- logging.warning(f"خطای NoAudioReceived برای VoiceID='{voice_id}' (تلاش {attempt + 1}/{MAX_TTS_RETRIES + 1}). متن: '{text_to_speak[:50]}...'. خطا: {e_no_audio}")
104
- if attempt < MAX_TTS_RETRIES:
105
- logging.info(f"تلاش مجدد پس از {TTS_RETRY_DELAY} ثانیه...")
106
- await asyncio.sleep(TTS_RETRY_DELAY)
107
- else:
108
- logging.error(f"خطای NoAudioReceived پس از {MAX_TTS_RETRIES + 1} تلاش برای VoiceID='{voice_id}'.")
109
- return f"خطای TTS: صدایی برای '{tts_voice_key}' دریافت نشد (NoAudioReceived). این صدا ممکن است موقتا در دسترس نباشد. لطفاً با صدای دیگری یا کمی بعدتر امتحان کنید.", None
110
 
111
- except Exception as e:
112
- logging.error(f"خطای Edge-TTS (تلاش {attempt + 1}) برای VoiceID='{voice_id}', VoiceKey='{tts_voice_key}': {e}\n{traceback.format_exc()}")
113
- return f"خطای TTS ({type(e).__name__}): مشکلی در تولید صدا برای '{tts_voice_key}' پیش آمد.", None
 
 
 
 
114
 
115
- return "خطای TTS: تلاش‌ها برای تولید صدا ناموفق بود.", None # Should not be reached
 
 
 
116
 
117
  _event_loops_by_thread = {}
118
- def _get_or_create_event_loop():
119
  thread_id = threading.get_ident()
120
  if thread_id not in _event_loops_by_thread or _event_loops_by_thread[thread_id].is_closed():
121
- loop = asyncio.new_event_loop(); asyncio.set_event_loop(loop)
122
- _event_loops_by_thread[thread_id] = loop
 
 
 
 
 
 
 
123
  return _event_loops_by_thread[thread_id]
124
 
 
125
  def translate_and_speak_sync_wrapper(persian_text, english_tts_voice_key, rate, volume, pitch):
126
  loop = _get_or_create_event_loop()
127
  error_message_for_ui = ""
128
 
129
  if not GOOGLE_API_KEY or not model:
130
- error_message_for_ui = "خطا: سرویس ترجمه پیکربندی نشده است. لطفاً از تنظیم صحیح GOOGLE_API_KEY در Secrets اطمینان حاصل کنید."
131
  return error_message_for_ui, None
132
  if not persian_text or not persian_text.strip():
133
  error_message_for_ui = "لطفاً متن فارسی را برای ترجمه وارد کنید."
134
  return error_message_for_ui, None
135
 
136
- translation_status_msg, translated_text = loop.run_until_complete(translate_text_gemini(persian_text, target_language="English"))
137
  translated_text_output = translated_text if translated_text else "ترجمه ناموفق بود."
138
 
139
  if "خطا" in translation_status_msg or not translated_text:
@@ -141,11 +141,13 @@ def translate_and_speak_sync_wrapper(persian_text, english_tts_voice_key, rate,
141
  return error_message_for_ui, None
142
 
143
  if english_tts_voice_key not in language_dict_persian_keys:
 
144
  if language_dict_persian_keys:
 
145
  english_tts_voice_key = list(language_dict_persian_keys.keys())[0]
146
- logging.warning(f"صدای انتخابی نامعتبر، به صدای پیشفرض {english_tts_voice_key} تغییر یافت.")
147
  else:
148
- error_message_for_ui = f"{translated_text_output}\n\n(خطای TTS: هیچ صدایی برای انتخاب موجود نیست.)"
149
  return error_message_for_ui, None
150
 
151
  tts_status_msg, audio_path = loop.run_until_complete(text_to_speech_edge_async(translated_text, english_tts_voice_key, rate, volume, pitch))
@@ -156,6 +158,7 @@ def translate_and_speak_sync_wrapper(persian_text, english_tts_voice_key, rate,
156
 
157
  return translated_text_output, audio_path
158
 
 
159
  FLY_PRIMARY_COLOR_HEX = "#4F46E5"
160
  FLY_SECONDARY_COLOR_HEX = "#10B981"
161
  FLY_ACCENT_COLOR_HEX = "#D97706"
@@ -224,6 +227,7 @@ if not language_dict_persian_keys:
224
  logging.critical("خطای بحرانی: language_dict_persian_keys خالی است!")
225
  default_english_tts_voice = None
226
  elif default_english_tts_voice not in language_dict_persian_keys:
 
227
  default_english_tts_voice = list(language_dict_persian_keys.keys())[0] if language_dict_persian_keys else "لیست خالی"
228
 
229
  logging.info(f"Gradio version: {gr.__version__}")
@@ -253,13 +257,18 @@ with gr.Blocks(theme=app_theme_outer, css=custom_css, title="آموزش زبان
253
  )
254
 
255
  dropdown_label = "🗣️ انتخاب گوینده و لهجه انگلیسی"
256
- if not language_dict_persian_keys: dropdown_label += " (لیست خالی!)"
 
 
 
 
 
257
 
258
  language_dropdown_tts_english = gr.Dropdown(
259
- choices=list(language_dict_persian_keys.keys()) if language_dict_persian_keys else ["لیست صداها خالی است"],
260
- value=default_english_tts_voice if default_english_tts_voice else "لیست صداها خال�� است",
261
  label=dropdown_label,
262
- interactive=bool(language_dict_persian_keys)
263
  )
264
  with gr.Accordion("⚙️ تنظیمات پیشرفته صدا", open=False):
265
  with gr.Row():
@@ -288,11 +297,15 @@ with gr.Blocks(theme=app_theme_outer, css=custom_css, title="آموزش زبان
288
  gr.HTML("<hr class='custom-hr'>")
289
  num_voices = len(language_dict_persian_keys)
290
  voice_keys = list(language_dict_persian_keys.keys())
291
- voice1, voice2, voice3 = voice_keys[0], voice_keys[min(7, num_voices-1)], voice_keys[min(13, num_voices-1)]
 
 
 
 
292
  example_list = [
293
- ["قیمت این لباس چقدر است؟", voice1, 0, 0, 0],
294
- ["می‌توانید آدرس را روی نقشه به من نشان دهید؟", voice2, 0, 0, 0],
295
- ["ببخشید، متوجه نشدم. امکان دارد تکرار کنید؟", voice3, -10, 0, 0],
296
  ]
297
 
298
  gr.Examples(
@@ -304,7 +317,7 @@ with gr.Blocks(theme=app_theme_outer, css=custom_css, title="آموزش زبان
304
  label="💡 نمونه‌های کاربردی"
305
  )
306
  else:
307
- gr.Markdown("<p style='text-align:center; color:var(--fly-text-secondary); margin-top:1rem;'>نمونه‌ای برای نمایش موجود نیست (لیست صداها خالی است یا صدای پیش‌فرض معتبر نیست).</p>")
308
 
309
  gr.Markdown("<p class='app-footer-fly'>Fly Language Learning © 2025</p>")
310
 
 
23
  else:
24
  logging.warning("هشدار: کلید API گوگل (GOOGLE_API_KEY) تنظیم نشده است. قابلیت ترجمه غیرفعال خواهد بود.")
25
 
26
+ # !!! مهم: این دیکشنری را با دیکشنری دقیقی که در اسپیس قبلی شما کار می‌کند جایگزین کنید !!!
27
+ # این یک نسخه کامل‌تر از پاسخ‌های قبلی است، اما ممکن است با اسپیس شما متفاوت باشد.
28
  language_dict_persian_keys = {
29
  'انگلیسی (آمریکا) - جنی (زن)': 'en-US-JennyNeural', 'انگلیسی (آمریکا) - گای (مرد)': 'en-US-GuyNeural',
30
  'انگلیسی (آمریکا) - آنا (زن، صدای کودک)': 'en-US-AnaNeural', 'انگلیسی (آمریکا) - آریا (زن)': 'en-US-AriaNeural',
31
  'انگلیسی (آمریکا) - کریستوفر (مرد)': 'en-US-ChristopherNeural', 'انگلیسی (آمریکا) - اریک (مرد)': 'en-US-EricNeural',
32
  'انگلیسی (آمریکا) - میشل (زن)': 'en-US-MichelleNeural', 'انگلیسی (آمریکا) - راجر (مرد)': 'en-US-RogerNeural',
33
+ 'انگلیسی (آمریکا) - استفانی (زن)': 'en-US-SteffanNeural', 'انگلیسی مریکا) - جیسون (مرد)': 'en-US-JasonNeural',
 
34
  'انگلیسی (آمریکا) - براندون (مرد)': 'en-US-BrandonNeural', 'انگلیسی (آمریکا) - الیزابت (زن)': 'en-US-ElizabethNeural',
35
  'انگلیسی (آمریکا) - دیویس (مرد، جدید)': 'en-US-DavisNeural', 'انگلیسی (آمریکا) - جین (زن، جدید)': 'en-US-JaneNeural',
36
  'انگلیسی (آمریکا) - نانسی (زن، جدید)': 'en-US-NancyNeural', 'انگلیسی (آمریکا) - سارا (زن، جدید)': 'en-US-SaraNeural',
 
45
  'انگلیسی (استرالیا) - کن (مرد، جدید)': 'en-AU-KenNeural', 'انگلیسی (استرالیا) - کیمبرلی (زن، جدید)': 'en-AU-KimNeural',
46
  'انگلیسی (استرالیا) - تینا (زن، جدید)': 'en-AU-TinaNeural',
47
  'انگلیسی (کانادا) - کلارا (زن)': 'en-CA-ClaraNeural', 'انگلیسی (کانادا) - لیام (مرد)': 'en-CA-LiamNeural',
48
+ 'انگلیسی (کانادا) - کوبی (مرد، جدید)': 'en-CA-CobiNeural', 'انگلیسی (کانادا) - ژان (مرد، جدید)': 'en-CA-JeanNeural',
 
49
  'انگلیسی (ایرلند) - امیلی (زن)': 'en-IE-EmilyNeural', 'انگلیسی (ایرلند) - کانر (مرد)': 'en-IE-ConnorNeural',
50
  'انگلیسی (هند) - نیرجا (زن)': 'en-IN-NeerjaNeural', 'انگلیسی (هند) - پرابهات (مرد)': 'en-IN-PrabhatNeural',
51
+ 'انگلیسی (هند) - میتالی (زن، جدید)': 'en-IN-MithaliNeural',
52
  'انگلیسی (آفریقای جنوبی) - لیا (زن)': 'en-ZA-LeahNeural', 'انگلیسی (آفریقای جنوبی) - لوک (مرد)': 'en-ZA-LukeNeural',
53
  'انگلیسی (هنگ کنگ) - یان (زن)': 'en-HK-YanNeural', 'انگلیسی (هنگ کنگ) - سم (مرد)': 'en-HK-SamNeural',
54
  'انگلیسی (نیوزیلند) - هیزل (زن)': 'en-NZ-HazelNeural', 'انگلیسی (نیوزیلند) - میچل (مرد)': 'en-NZ-MitchellNeural',
 
57
  'انگلیسی (کنیا) - آسیلیا (زن)': 'en-KE-AsiliaNeural', 'انگلیسی (کنیا) - چیلمبا (مرد)': 'en-KE-ChilembaNeural',
58
  'انگلیسی (نیجریه) - ازینه (زن)': 'en-NG-EzinneNeural', 'انگلیسی (نیجریه) - آبئو (مرد)': 'en-NG-AbeoNeural',
59
  'انگلیسی (تانزانیا) - ایمانی (زن)': 'en-TZ-ImaniNeural', 'انگلیسی (تانزانیا) - الیمو (مرد)': 'en-TZ-ElimuNeural',
60
+ # ممکن است صداهای فارسی یا زبان‌های دیگر هم در لیست شما باشند
61
+ 'فارسی (ایران) - دل‌آرا (زن)': 'fa-IR-DilaraNeural',
62
+ 'فارسی (ایران) - فرید (مرد)': 'fa-IR-FaridNeural',
63
  }
64
 
65
 
66
  async def translate_text_gemini(text, target_language="English"):
67
+ if not model: return "خطا: سرویس ترجمه در دسترس نیست.", None
68
  if not text or not text.strip(): return "خطا: متنی برای ترجمه وارد نشده است.", None
69
  try:
70
  prompt = f"Translate the following Persian text to {target_language}. Provide only the translated English text, naturally and fluently, without any extra phrases, explanations, or markdown formatting. Be concise and accurate.\n\nPersian: \"{text}\"\n{target_language}:"
 
77
  logging.error(f"خطای Gemini: {e}\n{traceback.format_exc()}")
78
  return f"خطای ترجمه: {type(e).__name__}", None
79
 
 
 
 
80
  async def text_to_speech_edge_async(text_to_speak, tts_voice_key, rate, volume, pitch):
81
  voice_id = language_dict_persian_keys.get(tts_voice_key)
82
  if voice_id is None:
 
87
 
88
  if not text_to_speak or not text_to_speak.strip():
89
  return "خطای TTS: متن ترجمه شده برای خواندن خالی است.", None
90
+ try:
91
+ rate_str, volume_str, pitch_str = f"{int(rate):+g}%", f"{int(volume):+g}%", f"{int(pitch):+g}Hz"
92
+ communicate = edge_tts.Communicate(text_to_speak, voice_id, rate=rate_str, volume=volume_str, pitch=pitch_str)
93
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
94
+ tmp_path = tmp_file.name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
95
 
96
+ await communicate.save(tmp_path)
97
+ logging.info(f"صدای '{voice_id}' با موفقیت در '{tmp_path}' ذخیره شد.")
98
+ return "TTS موفق", tmp_path
99
+
100
+ except edge_tts.exceptions.NoAudioReceived as e_no_audio:
101
+ logging.error(f"خطای NoAudioReceived برای VoiceID='{voice_id}', VoiceKey='{tts_voice_key}'. متن: '{text_to_speak[:50]}...'. خطا: {e_no_audio}")
102
+ return f"خطای TTS: صدایی برای '{tts_voice_key}' دریافت نشد (NoAudioReceived). ممکن است این صدا موقتا در دسترس نباشد یا مشکلی در سرویس TTS وجود داشته باشد.", None
103
 
104
+ except Exception as e:
105
+ logging.error(f"خطای Edge-TTS برای VoiceID='{voice_id}', VoiceKey='{tts_voice_key}': {e}\n{traceback.format_exc()}")
106
+ return f"خطای TTS ({type(e).__name__}): مشکلی در تولید صدا برای '{tts_voice_key}' پیش آمد.", None
107
+
108
 
109
  _event_loops_by_thread = {}
110
+ def _get_or_create_event_loop(): # تابع از کد نمونه شما
111
  thread_id = threading.get_ident()
112
  if thread_id not in _event_loops_by_thread or _event_loops_by_thread[thread_id].is_closed():
113
+ new_loop = asyncio.new_event_loop()
114
+ asyncio.set_event_loop(new_loop)
115
+ _event_loops_by_thread[thread_id] = new_loop
116
+ logging.info(f"Created new event loop {new_loop} for thread {thread_id}")
117
+ else:
118
+ # Ensure the existing loop is set as the current loop for this thread
119
+ # This handles cases where Gradio might switch contexts
120
+ asyncio.set_event_loop(_event_loops_by_thread[thread_id])
121
+ logging.debug(f"Reusing event loop {_event_loops_by_thread[thread_id]} for thread {thread_id}")
122
  return _event_loops_by_thread[thread_id]
123
 
124
+
125
  def translate_and_speak_sync_wrapper(persian_text, english_tts_voice_key, rate, volume, pitch):
126
  loop = _get_or_create_event_loop()
127
  error_message_for_ui = ""
128
 
129
  if not GOOGLE_API_KEY or not model:
130
+ error_message_for_ui = "خطا: سرویس ترجمه پیکربندی نشده است. لطفاً GOOGLE_API_KEY را بررسی کنید."
131
  return error_message_for_ui, None
132
  if not persian_text or not persian_text.strip():
133
  error_message_for_ui = "لطفاً متن فارسی را برای ترجمه وارد کنید."
134
  return error_message_for_ui, None
135
 
136
+ translation_status_msg, translated_text = loop.run_until_complete(translate_text_gemini(persian_text))
137
  translated_text_output = translated_text if translated_text else "ترجمه ناموفق بود."
138
 
139
  if "خطا" in translation_status_msg or not translated_text:
 
141
  return error_message_for_ui, None
142
 
143
  if english_tts_voice_key not in language_dict_persian_keys:
144
+ # اگر دیکشنری صداها خالی است یا کلید معتبر نیست، از اولین صدای موجود استفاده کن (اگر وجود داشته باشد)
145
  if language_dict_persian_keys:
146
+ original_choice = english_tts_voice_key
147
  english_tts_voice_key = list(language_dict_persian_keys.keys())[0]
148
+ logging.warning(f"صدای '{original_choice}' نامعتبر بود یا یافت نشد. به صدای پیشفرض '{english_tts_voice_key}' تغییر یافت.")
149
  else:
150
+ error_message_for_ui = f"{translated_text_output}\n\n(خطای TTS: لیست صداها خالی است.)"
151
  return error_message_for_ui, None
152
 
153
  tts_status_msg, audio_path = loop.run_until_complete(text_to_speech_edge_async(translated_text, english_tts_voice_key, rate, volume, pitch))
 
158
 
159
  return translated_text_output, audio_path
160
 
161
+
162
  FLY_PRIMARY_COLOR_HEX = "#4F46E5"
163
  FLY_SECONDARY_COLOR_HEX = "#10B981"
164
  FLY_ACCENT_COLOR_HEX = "#D97706"
 
227
  logging.critical("خطای بحرانی: language_dict_persian_keys خالی است!")
228
  default_english_tts_voice = None
229
  elif default_english_tts_voice not in language_dict_persian_keys:
230
+ # اگر صدای پیشفرض انگلیسی در لیست نبود، اولین مورد لیست کلی را انتخاب کن (می‌تواند فارسی باشد)
231
  default_english_tts_voice = list(language_dict_persian_keys.keys())[0] if language_dict_persian_keys else "لیست خالی"
232
 
233
  logging.info(f"Gradio version: {gr.__version__}")
 
257
  )
258
 
259
  dropdown_label = "🗣️ انتخاب گوینده و لهجه انگلیسی"
260
+ # اطمینان از اینکه گزینه "لیست خالی" فقط زمانی نمایش داده می شود که دیکشنری واقعا خالی است
261
+ dropdown_choices = list(language_dict_persian_keys.keys()) if language_dict_persian_keys else ["لیست صداها خالی است"]
262
+ dropdown_value = default_english_tts_voice
263
+ if not language_dict_persian_keys:
264
+ dropdown_label += " (لیست خالی!)"
265
+ dropdown_value = "لیست صداها خالی است"
266
 
267
  language_dropdown_tts_english = gr.Dropdown(
268
+ choices=dropdown_choices,
269
+ value=dropdown_value,
270
  label=dropdown_label,
271
+ interactive=bool(language_dict_persian_keys) # interactive فقط اگر صدا وجود دارد
272
  )
273
  with gr.Accordion("⚙️ تنظیمات پیشرفته صدا", open=False):
274
  with gr.Row():
 
297
  gr.HTML("<hr class='custom-hr'>")
298
  num_voices = len(language_dict_persian_keys)
299
  voice_keys = list(language_dict_persian_keys.keys())
300
+ # انتخاب صداها برای نمونه‌ها با دقت بیشتر برای جلوگیری از خطا
301
+ voice1_idx = 0
302
+ voice2_idx = min(7, num_voices - 1) if num_voices > 1 else 0
303
+ voice3_idx = min(13, num_voices - 1) if num_voices > 1 else 0
304
+
305
  example_list = [
306
+ ["قیمت این لباس چقدر است؟", voice_keys[voice1_idx], 0, 0, 0],
307
+ ["می‌توانید آدرس را روی نقشه به من نشان دهید؟", voice_keys[voice2_idx], 0, 0, 0],
308
+ ["ببخشید، متوجه نشدم. امکان دارد تکرار کنید؟", voice_keys[voice3_idx], -10, 0, 0],
309
  ]
310
 
311
  gr.Examples(
 
317
  label="💡 نمونه‌های کاربردی"
318
  )
319
  else:
320
+ gr.Markdown("<p style='text-align:center; color:var(--fly-text-secondary); margin-top:1rem;'>نمونه‌ای برای نمایش موجود نیست.</p>")
321
 
322
  gr.Markdown("<p class='app-footer-fly'>Fly Language Learning © 2025</p>")
323