Hamed744 commited on
Commit
ca27f4b
·
verified ·
1 Parent(s): a7ff8b9

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +50 -57
app.py CHANGED
@@ -9,7 +9,7 @@ import google.generativeai as genai
9
  import logging
10
 
11
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(threadName)s - %(message)s')
12
- logging.info(f"Gradio version: {gr.__version__}")
13
 
14
  GOOGLE_API_KEY = os.environ.get('GOOGLE_API_KEY')
15
  model = None
@@ -77,10 +77,9 @@ async def text_to_speech_edge_tts_async(text_to_speak, tts_voice_key, rate, volu
77
  try:
78
  rate_str, volume_str, pitch_str = f"{int(rate):+g}%", f"{int(volume):+g}%", f"{int(pitch):+g}Hz"
79
  communicate = edge_tts.Communicate(text_to_speak, voice_id, rate=rate_str, volume=volume_str, pitch=pitch_str)
80
- # Ensure tmp_path is correctly defined and used
81
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
82
  tmp_path = tmp_file.name
83
- await communicate.save(tmp_path) # save to the path
84
  return "TTS موفق", tmp_path
85
  except Exception as e: logging.error(f"Edge-TTS error: {e}", exc_info=True); return f"خطا در TTS: {type(e).__name__}", None
86
 
@@ -95,7 +94,7 @@ def get_event_loop():
95
  current_loop = asyncio.get_event_loop_policy().get_event_loop()
96
  if current_loop is not _thread_local.loop:
97
  asyncio.set_event_loop(_thread_local.loop)
98
- except RuntimeError: # No current event loop or loop is different
99
  asyncio.set_event_loop(_thread_local.loop)
100
  logging.info(f"Set event loop for thread {threading.get_ident()} as no current loop was found or it was different.")
101
  return _thread_local.loop
@@ -105,7 +104,7 @@ def translate_and_speak_sync_wrapper(persian_text, english_tts_voice_key, rate,
105
  logging.info(f"Wrapper called. Text: '{persian_text[:30]}...' Voice: {english_tts_voice_key}")
106
  loop = get_event_loop()
107
 
108
- audio_file_to_clean = None # Variable to store path for cleanup
109
 
110
  if not GOOGLE_API_KEY or not model:
111
  msg = "خطا: سرویس ترجمه پیکربندی نشده است. لطفاً از تنظیم GOOGLE_API_KEY اطمینان حاصل کنید."
@@ -115,7 +114,6 @@ def translate_and_speak_sync_wrapper(persian_text, english_tts_voice_key, rate,
115
  logging.warning(msg); return msg, None
116
  if not english_tts_voice_key or english_tts_voice_key == "لیست خالی":
117
  msg = "لطفاً یک صدای گوینده انتخاب کنید."
118
- # Return Persian text if no voice selected, so user sees their input
119
  logging.warning(msg); return persian_text, None
120
 
121
  try:
@@ -132,15 +130,14 @@ def translate_and_speak_sync_wrapper(persian_text, english_tts_voice_key, rate,
132
 
133
  if "خطا" in tts_status_msg.lower() or not audio_path:
134
  err_msg = f"{translated_text}\n(خطای TTS: {tts_status_msg})"
135
- logging.error(err_msg); return err_msg, None # Return translated text with TTS error
136
 
137
- audio_file_to_clean = audio_path # Mark for potential cleanup if needed by Gradio
138
  logging.info(f"عملیات موفق. متن ترجمه شده: '{translated_text[:30]}...', مسیر صوت: {audio_path}")
139
  return translated_text, audio_path
140
 
141
  except Exception as e:
142
  logging.error(f"خطای غیرمنتظره در wrapper: {e}", exc_info=True)
143
- # Clean up temp file if an unexpected error occurs after its creation
144
  if audio_file_to_clean and os.path.exists(audio_file_to_clean):
145
  try:
146
  os.remove(audio_file_to_clean)
@@ -152,14 +149,14 @@ def translate_and_speak_sync_wrapper(persian_text, english_tts_voice_key, rate,
152
  # --- تعریف تم و CSS بهبود یافته ---
153
  FLY_PRIMARY_COLOR_HEX = "#2563EB"
154
  FLY_SECONDARY_COLOR_HEX = "#059669"
155
- FLY_ACCENT_COLOR_HEX = "#D97706" # نارنجی برای دکمه اصلی
156
  FLY_TEXT_COLOR_HEX = "#1F2937"
157
  FLY_SUBTLE_TEXT_HEX = "#6B7280"
158
- FLY_LIGHT_BACKGROUND_HEX = "#F3F4F6" # کمی روشن‌تر از قبل
159
  FLY_WHITE_HEX = "#FFFFFF"
160
  FLY_BORDER_COLOR_HEX = "#D1D5DB"
161
  FLY_INPUT_BG_HEX = "#FFFFFF"
162
- FLY_PANEL_BG_HEX = "#E0F2FE" # آبی بسیار روشن برای پنل ترجمه
163
  FLY_SUCCESS_GREEN_HEX = "#059669"
164
  FLY_ERROR_RED_HEX = "#DC2626"
165
 
@@ -175,9 +172,6 @@ app_theme = gr.themes.Soft(
175
  button_primary_text_color=FLY_WHITE_HEX,
176
  )
177
 
178
- # --- CSS بهبود یافته با دقت در f-string ---
179
- # IMPORTANT: Using ''' """ ''' to define the multi-line string to avoid f-string parsing issues with CSS's own curly braces.
180
- # If we were using f-string for CSS, all CSS '{' and '}' would need to be '{{' and '}}'.
181
  custom_css = f"""
182
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700&display=swap');
183
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap');
@@ -254,7 +248,7 @@ body {{
254
  flex-grow: 1;
255
  padding: 0 1rem;
256
  }}
257
- .content-wrapper {{
258
  background-color: var(--fly-white);
259
  padding: 1.5rem;
260
  border-radius: var(--border-radius-lg);
@@ -271,7 +265,7 @@ body {{
271
  display: inline-block;
272
  }}
273
  .gr-input > label + div > textarea,
274
- .gr-dropdown > label + div > div > input, /* For searchable dropdown */
275
  .gr-dropdown > label + div > div > select {{
276
  border-radius: var(--border-radius-md) !important;
277
  border: 1px solid var(--fly-border) !important;
@@ -295,11 +289,9 @@ body {{
295
  }}
296
  .gr-dropdown svg.icon {{
297
  right: auto !important;
298
- left: 10px !important; /* برای RTL */
299
  }}
300
 
301
- /* Output Textbox styling */
302
- /* Targeting both old and new Gradio versions for output textbox */
303
  .gr-textbox[label*="ترجمه انگلیسی"] > label + div > textarea,
304
  .gradio-interface .output_text .gr-textbox[data-testid="textbox"] > label + div > textarea {{
305
  background-color: var(--fly-panel-bg) !important;
@@ -311,8 +303,7 @@ body {{
311
  font-weight: 500;
312
  }}
313
 
314
- /* Accordion styling */
315
- .gr-accordion > button.gr-button {{ /* Accordion Header Button */
316
  font-weight: 600 !important;
317
  padding: 10px 12px !important;
318
  border-radius: var(--border-radius-md) !important;
@@ -326,7 +317,7 @@ body {{
326
  .gr-accordion > button.gr-button:hover {{
327
  background-color: #D1D5DB !important;
328
  }}
329
- .gr-accordion > div.gr-panel {{ /* Accordion Content Panel */
330
  border-radius: var(--border-radius-md) !important;
331
  border: 1px solid var(--fly-border) !important;
332
  background-color: var(--fly-white) !important;
@@ -341,8 +332,8 @@ body {{
341
  color: var(--fly-subtle-text);
342
  }}
343
 
344
- /* Submit Button - Relies on app_theme for primary color */
345
- .fly-submit-button .gr-button {{
346
  width: 100% !important;
347
  padding: 12px 20px !important;
348
  font-size: 1em !important;
@@ -353,12 +344,12 @@ body {{
353
  letter-spacing: 0.5px;
354
  transition: background-color 0.2s ease, transform 0.1s ease;
355
  }}
356
- .fly-submit-button .gr-button:hover {{
357
  transform: translateY(-2px);
358
  }}
359
 
360
  /* Examples Section */
361
- div[id*="examples_section"] {{ /* More specific selector */
362
  margin-top: 1.5rem;
363
  }}
364
  div[id*="examples_section"] > .gr-panel {{
@@ -367,8 +358,8 @@ div[id*="examples_section"] > .gr-panel {{
367
  padding: 0 !important;
368
  box-shadow: none !important;
369
  }}
370
- div[id*="examples_section"] > .gr-form > button.gr-button.gr-button-secondary, /* Gradio 3.x */
371
- div[id*="examples_section"] .gr-sample-button /* Gradio 4.x */ {{
372
  background-color: #E0E7FF !important;
373
  color: var(--fly-primary) !important;
374
  border-radius: var(--border-radius-sm) !important;
@@ -388,7 +379,6 @@ div[id*="examples_section"] .gr-samples-header {{
388
  margin-bottom: 0.8rem;
389
  }}
390
 
391
-
392
  .custom-hr-fly {{
393
  height: 1px;
394
  background-color: var(--fly-border);
@@ -417,7 +407,6 @@ footer, .gradio-footer {{
417
  display: none !important;
418
  }}
419
 
420
- /* Desktop improvements */
421
  @media (min-width: 768px) {{
422
  body {{font-size: 16px;}}
423
  .gradio-container {{
@@ -430,7 +419,7 @@ footer, .gradio-footer {{
430
  }}
431
  .app-header-fly {{
432
  border-radius: var(--border-radius-lg);
433
- margin: -1.5rem -1.5rem 2rem -1.5rem; /* Extend to edges of container */
434
  }}
435
  .app-header-fly h1 {{ font-size: 2em !important; }}
436
  .app-header-fly p {{ font-size: 1em !important; }}
@@ -442,13 +431,13 @@ footer, .gradio-footer {{
442
  gap: 1.5rem;
443
  }}
444
  .main-content-columns-fly > .gr-column:nth-child(1) {{
445
- flex: 3; /* Input column wider */
446
  }}
447
  .main-content-columns-fly > .gr-column:nth-child(2) {{
448
- flex: 2; /* Output column */
449
  }}
450
 
451
- .fly-submit-button .gr-button {{
452
  width: auto !important;
453
  min-width: 200px;
454
  }}
@@ -462,11 +451,13 @@ footer, .gradio-footer {{
462
  .result-area-fly {{
463
  margin-top: 1rem;
464
  }}
465
- .result-area-fly > .gr-block > .gr-form > .gr-input,
466
- .result-area-fly > .gr-block > .gr-form > .gr-audio {{ /* Ensure output elements also have some margin */
 
 
467
  margin-bottom: 0.8rem;
468
  }}
469
- """ # End of f-string for CSS
470
 
471
  default_english_tts_voice = 'انگلیسی (آمریکا) - جنی (زن)'
472
  if not language_dict_persian_keys :
@@ -475,7 +466,6 @@ if not language_dict_persian_keys :
475
  elif default_english_tts_voice not in language_dict_persian_keys:
476
  default_english_tts_voice = list(language_dict_persian_keys.keys())[0] if language_dict_persian_keys else None
477
 
478
- # --- Gradio UI با ساختار بهبود یافته ---
479
  with gr.Blocks(theme=app_theme, css=custom_css, title="آموزش زبان فلای | Fly Language Learning") as demo:
480
  gr.HTML(f"""<div class="app-header-fly"><h1>Fly Language Learning 🚀</h1><p>ترجمه فارسی به انگلیسی و تلفظ با لهجه دلخواه</p></div>""")
481
 
@@ -486,7 +476,7 @@ with gr.Blocks(theme=app_theme, css=custom_css, title="آموزش زبان فل
486
  elif not model: missing_key_msg += "خطا در بارگذاری مدل Gemini."
487
  gr.Markdown(f"<div class='api-warning-message-fly'>{missing_key_msg} بخش ترجمه غیرفعال خواهد بود. لطفاً تنظیمات Secrets را بررسی کنید.</div>")
488
 
489
- with gr.Box(elem_classes=["content-wrapper"]): # Wrapper for card styling
490
  with gr.Row(elem_classes=["main-content-columns-fly"]):
491
  with gr.Column(scale=3):
492
  input_text_persian = gr.Textbox(
@@ -500,12 +490,14 @@ with gr.Blocks(theme=app_theme, css=custom_css, title="آموزش زبان فل
500
  interactive=bool(language_dict_persian_keys and default_english_tts_voice is not None)
501
  )
502
  with gr.Accordion("⚙️ تنظیمات پیشرفته صدا", open=False):
 
503
  rate_slider = gr.Slider(minimum=-100, maximum=100, value=0, step=10, label="سرعت گفتار (Rate)")
504
  volume_slider = gr.Slider(minimum=-100, maximum=100, value=0, step=10, label="بلندی صدا (Volume)")
505
  pitch_slider = gr.Slider(minimum=-50, maximum=50, value=0, step=5, label="زیر و بمی صدا (Pitch)")
506
 
507
- with gr.Box(elem_classes=["fly-submit-button"]):
508
- submit_button = gr.Button("ترجمه و پخش صدا", variant="primary", icon="▶️") # type: ignore
 
509
 
510
  with gr.Column(scale=2, elem_classes=["result-area-fly"]):
511
  output_text_translated = gr.Textbox(
@@ -520,24 +512,23 @@ with gr.Blocks(theme=app_theme, css=custom_css, title="آموزش زبان فل
520
  num_voices = len(language_dict_persian_keys)
521
  voice_keys = list(language_dict_persian_keys.keys())
522
 
523
- voice1_idx = 0
524
- voice2_idx = min(7, num_voices - 1) if num_voices > 1 else 0
525
- voice3_idx = min(13, num_voices - 1) if num_voices > 1 else 0
526
-
527
- example_list_data = [
528
- ["قیمت این لباس چقدر است؟", voice_keys[voice1_idx], 0, 0, 0],
529
- ["می‌توانید آدرس را روی نقشه به من نشان دهید؟", voice_keys[voice2_idx], 5, 0, 0],
530
- ["ببخشید، متوجه نشدم. امکان دارد تکرار کنید؟", voice_keys[voice3_idx], -10, 10, 0],
531
- ["یک قهوه و یک کیک لطفا.", voice_keys[min(3, num_voices -1) if num_voices > 0 else 0], 0, 0, 0],
532
- ]
533
- # Filter out examples if voice keys are not valid (e.g. num_voices is 0)
534
  valid_example_list = []
535
  if num_voices > 0:
 
 
 
 
 
 
 
 
 
 
 
536
  valid_example_list = example_list_data
537
- else: # No voices, no examples
538
  logging.warning("No voices available for examples.")
539
 
540
-
541
  if valid_example_list:
542
  gr.Examples(
543
  examples=valid_example_list,
@@ -550,14 +541,16 @@ with gr.Blocks(theme=app_theme, css=custom_css, title="آموزش زبان فل
550
 
551
  gr.Markdown("<p class='app-footer-fly'>✨ Fly Language Learning © ۲۰۲۴ - قدرت گرفته از Gemini و EdgeTTS ✨</p>")
552
 
553
- if 'submit_button' in locals(): # Check if button is defined (it should be)
 
 
554
  submit_button.click(
555
  fn=translate_and_speak_sync_wrapper,
556
  inputs=[input_text_persian, language_dropdown_tts_english, rate_slider, volume_slider, pitch_slider],
557
  outputs=[output_text_translated, output_audio]
558
  )
559
  else:
560
- logging.error("Submit button was not initialized correctly.")
561
 
562
 
563
  if __name__ == "__main__":
 
9
  import logging
10
 
11
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(threadName)s - %(message)s')
12
+ logging.info(f"Gradio version: {gr.__version__}") # This will show the actual version used by HF Spaces
13
 
14
  GOOGLE_API_KEY = os.environ.get('GOOGLE_API_KEY')
15
  model = None
 
77
  try:
78
  rate_str, volume_str, pitch_str = f"{int(rate):+g}%", f"{int(volume):+g}%", f"{int(pitch):+g}Hz"
79
  communicate = edge_tts.Communicate(text_to_speak, voice_id, rate=rate_str, volume=volume_str, pitch=pitch_str)
 
80
  with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file:
81
  tmp_path = tmp_file.name
82
+ await communicate.save(tmp_path)
83
  return "TTS موفق", tmp_path
84
  except Exception as e: logging.error(f"Edge-TTS error: {e}", exc_info=True); return f"خطا در TTS: {type(e).__name__}", None
85
 
 
94
  current_loop = asyncio.get_event_loop_policy().get_event_loop()
95
  if current_loop is not _thread_local.loop:
96
  asyncio.set_event_loop(_thread_local.loop)
97
+ except RuntimeError:
98
  asyncio.set_event_loop(_thread_local.loop)
99
  logging.info(f"Set event loop for thread {threading.get_ident()} as no current loop was found or it was different.")
100
  return _thread_local.loop
 
104
  logging.info(f"Wrapper called. Text: '{persian_text[:30]}...' Voice: {english_tts_voice_key}")
105
  loop = get_event_loop()
106
 
107
+ audio_file_to_clean = None
108
 
109
  if not GOOGLE_API_KEY or not model:
110
  msg = "خطا: سرویس ترجمه پیکربندی نشده است. لطفاً از تنظیم GOOGLE_API_KEY اطمینان حاصل کنید."
 
114
  logging.warning(msg); return msg, None
115
  if not english_tts_voice_key or english_tts_voice_key == "لیست خالی":
116
  msg = "لطفاً یک صدای گوینده انتخاب کنید."
 
117
  logging.warning(msg); return persian_text, None
118
 
119
  try:
 
130
 
131
  if "خطا" in tts_status_msg.lower() or not audio_path:
132
  err_msg = f"{translated_text}\n(خطای TTS: {tts_status_msg})"
133
+ logging.error(err_msg); return err_msg, None
134
 
135
+ audio_file_to_clean = audio_path
136
  logging.info(f"عملیات موفق. متن ترجمه شده: '{translated_text[:30]}...', مسیر صوت: {audio_path}")
137
  return translated_text, audio_path
138
 
139
  except Exception as e:
140
  logging.error(f"خطای غیرمنتظره در wrapper: {e}", exc_info=True)
 
141
  if audio_file_to_clean and os.path.exists(audio_file_to_clean):
142
  try:
143
  os.remove(audio_file_to_clean)
 
149
  # --- تعریف تم و CSS بهبود یافته ---
150
  FLY_PRIMARY_COLOR_HEX = "#2563EB"
151
  FLY_SECONDARY_COLOR_HEX = "#059669"
152
+ FLY_ACCENT_COLOR_HEX = "#D97706"
153
  FLY_TEXT_COLOR_HEX = "#1F2937"
154
  FLY_SUBTLE_TEXT_HEX = "#6B7280"
155
+ FLY_LIGHT_BACKGROUND_HEX = "#F3F4F6"
156
  FLY_WHITE_HEX = "#FFFFFF"
157
  FLY_BORDER_COLOR_HEX = "#D1D5DB"
158
  FLY_INPUT_BG_HEX = "#FFFFFF"
159
+ FLY_PANEL_BG_HEX = "#E0F2FE"
160
  FLY_SUCCESS_GREEN_HEX = "#059669"
161
  FLY_ERROR_RED_HEX = "#DC2626"
162
 
 
172
  button_primary_text_color=FLY_WHITE_HEX,
173
  )
174
 
 
 
 
175
  custom_css = f"""
176
  @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700&display=swap');
177
  @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap');
 
248
  flex-grow: 1;
249
  padding: 0 1rem;
250
  }}
251
+ .content-wrapper {{ /* This class will be applied to gr.Group */
252
  background-color: var(--fly-white);
253
  padding: 1.5rem;
254
  border-radius: var(--border-radius-lg);
 
265
  display: inline-block;
266
  }}
267
  .gr-input > label + div > textarea,
268
+ .gr-dropdown > label + div > div > input,
269
  .gr-dropdown > label + div > div > select {{
270
  border-radius: var(--border-radius-md) !important;
271
  border: 1px solid var(--fly-border) !important;
 
289
  }}
290
  .gr-dropdown svg.icon {{
291
  right: auto !important;
292
+ left: 10px !important;
293
  }}
294
 
 
 
295
  .gr-textbox[label*="ترجمه انگلیسی"] > label + div > textarea,
296
  .gradio-interface .output_text .gr-textbox[data-testid="textbox"] > label + div > textarea {{
297
  background-color: var(--fly-panel-bg) !important;
 
303
  font-weight: 500;
304
  }}
305
 
306
+ .gr-accordion > button.gr-button {{
 
307
  font-weight: 600 !important;
308
  padding: 10px 12px !important;
309
  border-radius: var(--border-radius-md) !important;
 
317
  .gr-accordion > button.gr-button:hover {{
318
  background-color: #D1D5DB !important;
319
  }}
320
+ .gr-accordion > div.gr-panel {{
321
  border-radius: var(--border-radius-md) !important;
322
  border: 1px solid var(--fly-border) !important;
323
  background-color: var(--fly-white) !important;
 
332
  color: var(--fly-subtle-text);
333
  }}
334
 
335
+ /* Submit Button */
336
+ .fly-submit-button-container .gr-button {{ /* Target the container of the button */
337
  width: 100% !important;
338
  padding: 12px 20px !important;
339
  font-size: 1em !important;
 
344
  letter-spacing: 0.5px;
345
  transition: background-color 0.2s ease, transform 0.1s ease;
346
  }}
347
+ .fly-submit-button-container .gr-button:hover {{
348
  transform: translateY(-2px);
349
  }}
350
 
351
  /* Examples Section */
352
+ div[id*="examples_section"] {{
353
  margin-top: 1.5rem;
354
  }}
355
  div[id*="examples_section"] > .gr-panel {{
 
358
  padding: 0 !important;
359
  box-shadow: none !important;
360
  }}
361
+ div[id*="examples_section"] > .gr-form > button.gr-button.gr-button-secondary,
362
+ div[id*="examples_section"] .gr-sample-button {{
363
  background-color: #E0E7FF !important;
364
  color: var(--fly-primary) !important;
365
  border-radius: var(--border-radius-sm) !important;
 
379
  margin-bottom: 0.8rem;
380
  }}
381
 
 
382
  .custom-hr-fly {{
383
  height: 1px;
384
  background-color: var(--fly-border);
 
407
  display: none !important;
408
  }}
409
 
 
410
  @media (min-width: 768px) {{
411
  body {{font-size: 16px;}}
412
  .gradio-container {{
 
419
  }}
420
  .app-header-fly {{
421
  border-radius: var(--border-radius-lg);
422
+ margin: -1.5rem -1.5rem 2rem -1.5rem;
423
  }}
424
  .app-header-fly h1 {{ font-size: 2em !important; }}
425
  .app-header-fly p {{ font-size: 1em !important; }}
 
431
  gap: 1.5rem;
432
  }}
433
  .main-content-columns-fly > .gr-column:nth-child(1) {{
434
+ flex: 3;
435
  }}
436
  .main-content-columns-fly > .gr-column:nth-child(2) {{
437
+ flex: 2;
438
  }}
439
 
440
+ .fly-submit-button-container .gr-button {{
441
  width: auto !important;
442
  min-width: 200px;
443
  }}
 
451
  .result-area-fly {{
452
  margin-top: 1rem;
453
  }}
454
+ .result-area-fly > .gr-block > .gr-form > .gr-input, /* Gradio < 4 */
455
+ .result-area-fly > div[data-testid="block"] > div[data-testid="form"] > .gr-input, /* Gradio 4+ */
456
+ .result-array-fly > .gr-block > .gr-form > .gr-audio, /* Gradio < 4 */
457
+ .result-area-fly > div[data-testid="block"] > div[data-testid="form"] > .gr-audio {{
458
  margin-bottom: 0.8rem;
459
  }}
460
+ """
461
 
462
  default_english_tts_voice = 'انگلیسی (آمریکا) - جنی (زن)'
463
  if not language_dict_persian_keys :
 
466
  elif default_english_tts_voice not in language_dict_persian_keys:
467
  default_english_tts_voice = list(language_dict_persian_keys.keys())[0] if language_dict_persian_keys else None
468
 
 
469
  with gr.Blocks(theme=app_theme, css=custom_css, title="آموزش زبان فلای | Fly Language Learning") as demo:
470
  gr.HTML(f"""<div class="app-header-fly"><h1>Fly Language Learning 🚀</h1><p>ترجمه فارسی به انگلیسی و تلفظ با لهجه دلخواه</p></div>""")
471
 
 
476
  elif not model: missing_key_msg += "خطا در بارگذاری مدل Gemini."
477
  gr.Markdown(f"<div class='api-warning-message-fly'>{missing_key_msg} بخش ترجمه غیرفعال خواهد بود. لطفاً تنظیمات Secrets را بررسی کنید.</div>")
478
 
479
+ with gr.Group(elem_classes=["content-wrapper"]): # CHANGED: gr.Box to gr.Group
480
  with gr.Row(elem_classes=["main-content-columns-fly"]):
481
  with gr.Column(scale=3):
482
  input_text_persian = gr.Textbox(
 
490
  interactive=bool(language_dict_persian_keys and default_english_tts_voice is not None)
491
  )
492
  with gr.Accordion("⚙️ تنظیمات پیشرفته صدا", open=False):
493
+ # Using min, max, value for wider compatibility with older Gradio versions if `minimum` etc. are not supported
494
  rate_slider = gr.Slider(minimum=-100, maximum=100, value=0, step=10, label="سرعت گفتار (Rate)")
495
  volume_slider = gr.Slider(minimum=-100, maximum=100, value=0, step=10, label="بلندی صدا (Volume)")
496
  pitch_slider = gr.Slider(minimum=-50, maximum=50, value=0, step=5, label="زیر و بمی صدا (Pitch)")
497
 
498
+ # To apply class to button container, we wrap it in a Group/Column if direct elem_classes on button's parent isn't enough
499
+ with gr.Group(elem_classes=["fly-submit-button-container"]): # Using Group to contain the button for styling
500
+ submit_button = gr.Button("ترجمه و پخش صدا", variant="primary") # Icon removed for wider compatibility initially
501
 
502
  with gr.Column(scale=2, elem_classes=["result-area-fly"]):
503
  output_text_translated = gr.Textbox(
 
512
  num_voices = len(language_dict_persian_keys)
513
  voice_keys = list(language_dict_persian_keys.keys())
514
 
 
 
 
 
 
 
 
 
 
 
 
515
  valid_example_list = []
516
  if num_voices > 0:
517
+ voice1_idx = 0
518
+ voice2_idx = min(7, num_voices - 1)
519
+ voice3_idx = min(13, num_voices - 1)
520
+ voice4_idx = min(3, num_voices - 1)
521
+
522
+ example_list_data = [
523
+ ["قیمت این لباس چقدر است؟", voice_keys[voice1_idx], 0, 0, 0],
524
+ ["می‌توانید آدرس را روی نقشه به من نشان دهید؟", voice_keys[voice2_idx], 5, 0, 0],
525
+ ["ببخشید، متوجه نشدم. امکان دارد تکرار کنید؟", voice_keys[voice3_idx], -10, 10, 0],
526
+ ["یک قهوه و یک کیک لطفا.", voice_keys[voice4_idx], 0, 0, 0],
527
+ ]
528
  valid_example_list = example_list_data
529
+ else:
530
  logging.warning("No voices available for examples.")
531
 
 
532
  if valid_example_list:
533
  gr.Examples(
534
  examples=valid_example_list,
 
541
 
542
  gr.Markdown("<p class='app-footer-fly'>✨ Fly Language Learning © ۲۰۲۴ - قدرت گرفته از Gemini و EdgeTTS ✨</p>")
543
 
544
+ # Ensure submit_button is defined before calling .click
545
+ # This check is good practice but usually not strictly needed if layout is static
546
+ if 'submit_button' in locals() and submit_button is not None:
547
  submit_button.click(
548
  fn=translate_and_speak_sync_wrapper,
549
  inputs=[input_text_persian, language_dropdown_tts_english, rate_slider, volume_slider, pitch_slider],
550
  outputs=[output_text_translated, output_audio]
551
  )
552
  else:
553
+ logging.error("Submit button was not initialized correctly or is None.")
554
 
555
 
556
  if __name__ == "__main__":