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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +343 -226
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__}") # This will show the actual version used by HF Spaces
13
 
14
  GOOGLE_API_KEY = os.environ.get('GOOGLE_API_KEY')
15
  model = None
@@ -108,13 +108,13 @@ def translate_and_speak_sync_wrapper(persian_text, english_tts_voice_key, rate,
108
 
109
  if not GOOGLE_API_KEY or not model:
110
  msg = "خطا: سرویس ترجمه پیکربندی نشده است. لطفاً از تنظیم GOOGLE_API_KEY اطمینان حاصل کنید."
111
- logging.error(msg); return msg, None
112
  if not persian_text or not persian_text.strip():
113
  msg = "لطفاً متن فارسی را برای ترجمه وارد کنید."
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:
120
  translation_status_msg, translated_text = loop.run_until_complete(
@@ -122,19 +122,22 @@ def translate_and_speak_sync_wrapper(persian_text, english_tts_voice_key, rate,
122
  )
123
  if "خطا" in translation_status_msg.lower() or not translated_text:
124
  err_msg = f"ترجمه ناموفق: {translation_status_msg} {translated_text or ''}"
125
- logging.error(err_msg); return err_msg, None
126
 
 
 
 
127
  tts_status_msg, audio_path = loop.run_until_complete(
128
  text_to_speech_edge_tts_async(translated_text, english_tts_voice_key, rate, volume, pitch)
129
  )
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)
@@ -144,69 +147,75 @@ def translate_and_speak_sync_wrapper(persian_text, english_tts_voice_key, rate,
144
  logging.info(f"Cleaned up temporary audio file: {audio_file_to_clean}")
145
  except OSError as oe:
146
  logging.error(f"Error cleaning up temp file {audio_file_to_clean}: {oe}")
147
- return f"خطای داخلی سرور: {type(e).__name__}", None
148
-
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
-
163
- app_theme = gr.themes.Soft(
164
- primary_hue=gr.themes.colors.blue,
165
- secondary_hue=gr.themes.colors.emerald,
166
- neutral_hue=gr.themes.colors.slate,
167
- font=[gr.themes.GoogleFont("Vazirmatn"), "Arial", "sans-serif"],
168
  ).set(
169
  body_background_fill=FLY_LIGHT_BACKGROUND_HEX,
170
- button_primary_background_fill=FLY_ACCENT_COLOR_HEX,
171
- button_primary_background_fill_hover=f"{FLY_ACCENT_COLOR_HEX}E0",
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');
178
- @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
 
179
  :root {{
180
  --fly-primary: {FLY_PRIMARY_COLOR_HEX};
181
  --fly-secondary: {FLY_SECONDARY_COLOR_HEX};
182
  --fly-accent: {FLY_ACCENT_COLOR_HEX};
183
- --fly-text: {FLY_TEXT_COLOR_HEX};
184
- --fly-subtle-text: {FLY_SUBTLE_TEXT_HEX};
185
- --fly-light-bg: {FLY_LIGHT_BACKGROUND_HEX};
186
- --fly-white: {FLY_WHITE_HEX};
187
- --fly-border: {FLY_BORDER_COLOR_HEX};
188
- --fly-input-bg: {FLY_INPUT_BG_HEX};
189
  --fly-panel-bg: {FLY_PANEL_BG_HEX};
190
- --fly-success: {FLY_SUCCESS_GREEN_HEX};
191
- --fly-error: {FLY_ERROR_RED_HEX};
192
- --global-font: 'Vazirmatn', 'Inter', 'Poppins', Arial, sans-serif;
193
- --english-font: 'Poppins', 'Inter', Arial, sans-serif;
194
- --border-radius-sm: 6px;
195
- --border-radius-md: 8px;
196
- --border-radius-lg: 12px;
 
 
 
197
  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
198
- --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
 
 
 
 
 
199
  }}
 
200
  body {{
201
- font-family: var(--global-font);
202
  direction: rtl;
203
- background-color: var(--fly-light-bg);
204
- color: var(--fly-text);
205
  line-height: 1.7;
206
  -webkit-font-smoothing: antialiased;
207
  -moz-osx-font-smoothing: grayscale;
208
- font-size: 15px;
209
  }}
 
210
  .gradio-container {{
211
  max-width: 100% !important;
212
  width: 100% !important;
@@ -215,247 +224,323 @@ body {{
215
  padding: 0 !important;
216
  border-radius: 0 !important;
217
  box-shadow: none !important;
218
- background-color: var(--fly-light-bg) !important;
219
  display: flex;
220
  flex-direction: column;
221
  }}
222
 
223
- .app-header-fly {{
224
  text-align: center;
225
- padding: 24px 15px;
 
226
  background: linear-gradient(135deg, var(--fly-primary) 0%, var(--fly-secondary) 100%);
227
- color: white;
228
- margin: 0 0 1.5rem 0;
229
- border-bottom-left-radius: 20px;
230
- border-bottom-right-radius: 20px;
231
- box-shadow: 0 4px 12px -2px rgba(37, 99, 235, 0.2);
232
- }}
233
- .app-header-fly h1 {{
234
- font-size: 1.8em !important;
235
- font-weight: 700 !important;
236
- margin: 0 0 6px 0;
237
- font-family: var(--english-font);
238
- letter-spacing: 0.5px;
239
- }}
240
- .app-header-fly p {{
241
- font-size: 0.95em !important;
242
- margin-top: 4px;
 
 
 
 
 
 
 
 
 
 
 
 
243
  font-weight: 400;
244
- color: rgba(255, 255, 255, 0.9) !important;
245
  }}
246
 
247
  .main-content-area-fly {{
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);
255
- box-shadow: var(--shadow-md);
256
- margin-bottom: 1.5rem;
 
 
 
257
  }}
258
 
259
  /* --- Styling inputs --- */
260
- .gr-input > label > span.label-text, .gr-dropdown > label > span.label-text {{
 
 
261
  font-weight: 600 !important;
262
- color: #374151 !important;
263
  font-size: 0.9em !important;
264
- margin-bottom: 6px !important;
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;
272
- font-size: 0.95em !important;
273
  background-color: var(--fly-input-bg) !important;
274
- padding: 10px 12px !important;
275
  line-height: 1.6;
276
  width: 100% !important;
277
  box-sizing: border-box !important;
278
- -webkit-tap-highlight-color: transparent;
279
- font-family: var(--global-font) !important;
280
- color: var(--fly-text) !important;
281
- transition: border-color 0.2s ease, box-shadow 0.2s ease;
282
  }}
283
  .gr-input > label + div > textarea:focus,
284
  .gr-dropdown > label + div > div > input:focus,
285
  .gr-dropdown > label + div > div > select:focus {{
286
  border-color: var(--fly-primary) !important;
287
- box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2) !important;
288
  outline: none;
 
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;
298
- border-color: #A5D5FE !important;
299
- min-height: 100px;
300
- font-family: var(--english-font) !important;
301
- font-size: 1em !important;
302
- color: #0C4A6E !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;
310
- background-color: #E5E7EB !important;
311
- color: var(--fly-text) !important;
312
- border: 1px solid #D1D5DB !important;
313
- font-size: 0.9em;
314
  margin-bottom: 0.5rem;
315
- transition: background-color 0.2s ease;
 
316
  }}
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;
324
- padding: 1rem !important;
325
  box-shadow: none;
 
 
 
326
  }}
327
  .gr-slider {{
328
- padding: 8px 0 !important;
 
 
 
 
329
  }}
330
- .gr-slider label span {{
331
- font-size: 0.85em !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;
340
- font-weight: 600 !important;
341
- border-radius: var(--border-radius-md) !important;
342
- margin-top: 1rem !important;
343
- text-transform: uppercase;
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 {{
356
  background-color: transparent !important;
357
  border: none !important;
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;
366
- font-size: 0.85em !important;
367
- padding: 5px 10px !important;
368
- border: 1px solid #C7D2FE !important;
369
- transition: background-color 0.2s ease;
370
- }}
371
- div[id*="examples_section"] > .gr-form > button.gr-button.gr-button-secondary:hover,
372
- div[id*="examples_section"] .gr-sample-button:hover {{
373
- background-color: #C7D2FE !important;
374
- }}
375
- div[id*="examples_section"] .gr-samples-header {{
376
- font-weight: 600;
377
- color: #4B5563;
378
- font-size: 0.95em;
379
- margin-bottom: 0.8rem;
380
  }}
381
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
  .custom-hr-fly {{
383
  height: 1px;
384
- background-color: var(--fly-border);
385
- margin: 1.8rem 0;
386
  border: none;
387
  }}
388
  .api-warning-message-fly {{
389
- background-color: #FFFBEB !important;
390
- color: #92400E !important;
391
- padding: 12px 15px !important;
392
- border-radius: var(--border-radius-md) !important;
393
- border: 1px solid #FDE68A !important;
394
  text-align: center !important;
395
- margin: 0 0.5rem 1rem 0.5rem !important;
396
  font-size: 0.9em !important;
397
  font-weight: 500;
 
398
  }}
399
  .app-footer-fly {{
400
  text-align:center;
401
- font-size:0.8em;
402
- color: var(--fly-subtle-text);
403
- margin-top:2rem;
404
- padding-bottom: 1rem;
 
 
 
405
  }}
406
  footer, .gradio-footer {{
407
  display: none !important;
408
  }}
409
 
 
410
  @media (min-width: 768px) {{
411
- body {{font-size: 16px;}}
412
- .gradio-container {{
413
- max-width: 800px !important;
414
- padding: 1.5rem !important;
415
- margin: 2rem auto !important;
416
- background-color: var(--fly-light-bg) !important;
417
- border-radius: 0;
418
- box-shadow: none;
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; }}
426
- .main-content-area-fly {{padding: 0;}}
427
-
428
  .main-content-columns-fly {{
429
  display: flex;
430
  flex-direction: row;
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
  }}
444
- .api-warning-message-fly {{
445
- margin-left: 0 !important;
446
- margin-right: 0 !important;
447
- font-size: 0.9em;
448
- }}
449
- }}
450
-
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
 
@@ -466,8 +551,15 @@ 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
 
472
  with gr.Column(elem_classes=["main-content-area-fly"]):
473
  if not GOOGLE_API_KEY or not model:
@@ -476,39 +568,44 @@ with gr.Blocks(theme=app_theme, css=custom_css, title="آموزش زبان فل
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(
483
- lines=4, label="📝 متن فارسی خود را وارد کنید:", placeholder="مثال: سلام، حال شما چطور است؟",
 
484
  elem_id="persian_input_textbox"
485
  )
486
  language_dropdown_tts_english = gr.Dropdown(
487
  choices=list(language_dict_persian_keys.keys()) if language_dict_persian_keys else ["لیست خالی"],
488
  value=default_english_tts_voice if default_english_tts_voice else "لیست خالی",
489
- label="🗣️ گوینده و لهجه انگلیسی را انتخاب کنید:",
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(
504
- label="📜 ترجمه انگلیسی:", interactive=False, lines=5,
505
- placeholder="ترجمه متن شما در اینجا نمایش داده می‌شود...",
506
- elem_id="english_output_textbox"
507
- )
508
- output_audio = gr.Audio(label="🎧 فایل صوتی:", type="filepath", format="mp3", interactive=False)
 
 
 
 
 
509
 
510
  if language_dict_persian_keys and default_english_tts_voice:
511
- gr.HTML("<hr class='custom-hr-fly'>")
512
  num_voices = len(language_dict_persian_keys)
513
  voice_keys = list(language_dict_persian_keys.keys())
514
 
@@ -533,21 +630,41 @@ with gr.Blocks(theme=app_theme, css=custom_css, title="آموزش زبان فل
533
  gr.Examples(
534
  examples=valid_example_list,
535
  inputs=[input_text_persian, language_dropdown_tts_english, rate_slider, volume_slider, pitch_slider],
536
- outputs=[output_text_translated, output_audio],
 
537
  fn=translate_and_speak_sync_wrapper,
538
- label="💡 نمونه‌های آماده (برای امتحان کلیک کنید):",
539
- elem_id="examples_section"
540
  )
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.")
@@ -555,8 +672,8 @@ with gr.Blocks(theme=app_theme, css=custom_css, title="آموزش زبان فل
555
 
556
  if __name__ == "__main__":
557
  if not language_dict_persian_keys:
558
- logging.critical("CRITICAL: Voice dictionary (language_dict_persian_keys) is empty! The application might not function correctly.")
559
  elif not default_english_tts_voice:
560
- logging.warning("WARNING: No default English TTS voice could be set. Dropdown might be empty or non-interactive.")
561
 
562
- demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)
 
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__}") # Moved to after import to ensure gr is defined
13
 
14
  GOOGLE_API_KEY = os.environ.get('GOOGLE_API_KEY')
15
  model = None
 
108
 
109
  if not GOOGLE_API_KEY or not model:
110
  msg = "خطا: سرویس ترجمه پیکربندی نشده است. لطفاً از تنظیم GOOGLE_API_KEY اطمینان حاصل کنید."
111
+ logging.error(msg); return msg, None, None # Return three values for outputs
112
  if not persian_text or not persian_text.strip():
113
  msg = "لطفاً متن فارسی را برای ترجمه وارد کنید."
114
+ logging.warning(msg); return msg, None, None # Return three values for outputs
115
  if not english_tts_voice_key or english_tts_voice_key == "لیست خالی":
116
  msg = "لطفاً یک صدای گوینده انتخاب کنید."
117
+ logging.warning(msg); return persian_text, None, None # Return three values for outputs
118
 
119
  try:
120
  translation_status_msg, translated_text = loop.run_until_complete(
 
122
  )
123
  if "خطا" in translation_status_msg.lower() or not translated_text:
124
  err_msg = f"ترجمه ناموفق: {translation_status_msg} {translated_text or ''}"
125
+ logging.error(err_msg); return err_msg, None, gr.update(visible=False) # Return three values
126
 
127
+ # Show the translated text immediately
128
+ # gr.update for status_message will be handled in the main thread / by Gradio's event handling
129
+
130
  tts_status_msg, audio_path = loop.run_until_complete(
131
  text_to_speech_edge_tts_async(translated_text, english_tts_voice_key, rate, volume, pitch)
132
  )
133
 
134
  if "خطا" in tts_status_msg.lower() or not audio_path:
135
  err_msg = f"{translated_text}\n(خطای TTS: {tts_status_msg})"
136
+ logging.error(err_msg); return err_msg, None, gr.update(value=f"⚠️ خطای TTS: {tts_status_msg}", visible=True) # Return three values
137
 
138
  audio_file_to_clean = audio_path
139
  logging.info(f"عملیات موفق. متن ترجمه شده: '{translated_text[:30]}...', مسیر صوت: {audio_path}")
140
+ return translated_text, audio_path, gr.update(value="✅ ترجمه و تولید صدا موفقیت آمیز بود!", visible=True) # Return three values
141
 
142
  except Exception as e:
143
  logging.error(f"خطای غیرمنتظره در wrapper: {e}", exc_info=True)
 
147
  logging.info(f"Cleaned up temporary audio file: {audio_file_to_clean}")
148
  except OSError as oe:
149
  logging.error(f"Error cleaning up temp file {audio_file_to_clean}: {oe}")
150
+ return f"خطای داخلی سرور: {type(e).__name__}", None, gr.update(value=f"🚫 خطای داخلی: {type(e).__name__}", visible=True) # Return three values
151
+
152
+ # --- تعریف تم و CSS بسیار زیباتر ---
153
+ FLY_PRIMARY_COLOR_HEX = "#4F46E5" # Indigo
154
+ FLY_SECONDARY_COLOR_HEX = "#10B981" # Emerald
155
+ FLY_ACCENT_COLOR_HEX = "#F59E0B" # Amber
156
+ FLY_TEXT_COLOR_HEX = "#111827" # Gray 900
157
+ FLY_SUBTLE_TEXT_HEX = "#6B7280" # Gray 500
158
+ FLY_LIGHT_BACKGROUND_HEX = "#F9FAFB" # Gray 50
159
  FLY_WHITE_HEX = "#FFFFFF"
160
+ FLY_BORDER_COLOR_HEX = "#D1D5DB" # Gray 300
161
+ FLY_INPUT_BG_HEX = "#FFFFFF"
162
+ FLY_PANEL_BG_HEX = "#E0E7FF" # Indigo 100 (برای پنل ترجمه)
163
+
164
+ # برای استفاده از فونت وزیر در تم Gradio اگر به صورت مستقیم در GoogleFont کار نکند،
165
+ # باید از یک فونت جایگزین استاندارد استفاده کرد و سپس با CSS آن را override کرد.
166
+ app_theme = gr.themes.Base( # Using Base theme for more CSS control
167
+ font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"], # Primary font for Gradio's own elements
 
 
 
168
  ).set(
169
  body_background_fill=FLY_LIGHT_BACKGROUND_HEX,
170
+ # Button colors will be handled by custom CSS for more advanced styling (gradients, etc.)
 
 
171
  )
172
 
173
  custom_css = f"""
174
+ @import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@300;400;500;600;700;800&display=swap');
175
+ @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700;800&display=swap');
176
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
177
+
178
  :root {{
179
  --fly-primary: {FLY_PRIMARY_COLOR_HEX};
180
  --fly-secondary: {FLY_SECONDARY_COLOR_HEX};
181
  --fly-accent: {FLY_ACCENT_COLOR_HEX};
182
+ --fly-text-primary: {FLY_TEXT_COLOR_HEX};
183
+ --fly-text-secondary: {FLY_SUBTLE_TEXT_HEX};
184
+ --fly-bg-light: {FLY_LIGHT_BACKGROUND_HEX};
185
+ --fly-bg-white: {FLY_WHITE_HEX};
186
+ --fly-border-color: {FLY_BORDER_COLOR_HEX};
187
+ --fly-input-bg: {FLY_INPUT_BG_HEX};
188
  --fly-panel-bg: {FLY_PANEL_BG_HEX};
189
+
190
+ --font-global: 'Vazirmatn', 'Inter', 'Poppins', system-ui, sans-serif;
191
+ --font-english: 'Poppins', 'Inter', system-ui, sans-serif;
192
+
193
+ --radius-sm: 0.375rem; /* 6px */
194
+ --radius-md: 0.5rem; /* 8px */
195
+ --radius-lg: 0.75rem; /* 12px */
196
+ --radius-xl: 1rem; /* 16px */
197
+ --radius-full: 9999px;
198
+
199
  --shadow-sm: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
200
+ --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
201
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
202
+ --shadow-xl: 0 20px 25px -5px rgba(0,0,0,0.1), 0 8px 10px -6px rgba(0,0,0,0.1);
203
+
204
+ --transition-fast: all 0.15s ease-in-out;
205
+ --transition-normal: all 0.25s ease-in-out;
206
  }}
207
+
208
  body {{
209
+ font-family: var(--font-global);
210
  direction: rtl;
211
+ background-color: var(--fly-bg-light);
212
+ color: var(--fly-text-primary);
213
  line-height: 1.7;
214
  -webkit-font-smoothing: antialiased;
215
  -moz-osx-font-smoothing: grayscale;
216
+ font-size: 16px; /* Base font size */
217
  }}
218
+
219
  .gradio-container {{
220
  max-width: 100% !important;
221
  width: 100% !important;
 
224
  padding: 0 !important;
225
  border-radius: 0 !important;
226
  box-shadow: none !important;
227
+ background: linear-gradient(170deg, #E0F2FE 0%, #F3E8FF 100%); /* Light gradient background for the whole page */
228
  display: flex;
229
  flex-direction: column;
230
  }}
231
 
232
+ .app-title-card {{
233
  text-align: center;
234
+ padding: 2.5rem 1rem;
235
+ margin: 0;
236
  background: linear-gradient(135deg, var(--fly-primary) 0%, var(--fly-secondary) 100%);
237
+ color: var(--fly-bg-white);
238
+ border-bottom-left-radius: var(--radius-xl);
239
+ border-bottom-right-radius: var(--radius-xl);
240
+ box-shadow: var(--shadow-lg);
241
+ position: relative;
242
+ overflow: hidden;
243
+ }}
244
+ .app-title-card::before {{ /* Decorative element */
245
+ content: '';
246
+ position: absolute;
247
+ top: -50px; right: -50px;
248
+ width: 150px; height: 150px;
249
+ background: rgba(255,255,255,0.1);
250
+ border-radius: var(--radius-full);
251
+ opacity: 0.5;
252
+ transform: rotate(45deg);
253
+ }}
254
+ .app-title-card h1 {{
255
+ font-size: 2.25em !important; /* Larger title */
256
+ font-weight: 800 !important;
257
+ margin: 0 0 0.5rem 0;
258
+ font-family: var(--font-english);
259
+ letter-spacing: -0.5px;
260
+ text-shadow: 0 2px 4px rgba(0,0,0,0.1);
261
+ }}
262
+ .app-title-card p {{
263
+ font-size: 1em !important;
264
+ margin-top: 0.25rem;
265
  font-weight: 400;
266
+ color: rgba(255, 255, 255, 0.85) !important;
267
  }}
268
 
269
  .main-content-area-fly {{
270
  flex-grow: 1;
271
+ padding: 1.5rem; /* Add padding around the main content */
272
+ max-width: 900px; /* Max width for content on larger screens */
273
+ width: 100%;
274
+ margin: 0 auto; /* Center the content area */
275
  }}
276
+
277
+ .content-panel {{ /* Replaces content-wrapper, more generic name */
278
+ background-color: var(--fly-bg-white);
279
  padding: 1.5rem;
280
+ border-radius: var(--radius-xl);
281
+ box-shadow: var(--shadow-xl);
282
+ margin-top: -2rem; /* Overlap with header for a modern effect */
283
+ position: relative; /* For stacking context */
284
+ z-index: 10;
285
+ margin-bottom: 2rem;
286
  }}
287
 
288
  /* --- Styling inputs --- */
289
+ .gr-input > label > span.label-text,
290
+ .gr-dropdown > label > span.label-text,
291
+ .gr-slider > label > span.label-text {{
292
  font-weight: 600 !important;
293
+ color: var(--fly-text-primary) !important;
294
  font-size: 0.9em !important;
295
+ margin-bottom: 0.5rem !important;
296
+ display: block; /* Make label take full width */
297
  }}
298
+
299
  .gr-input > label + div > textarea,
300
  .gr-dropdown > label + div > div > input,
301
  .gr-dropdown > label + div > div > select {{
302
+ border-radius: var(--radius-md) !important;
303
+ border: 1px solid var(--fly-border-color) !important;
304
+ font-size: 1em !important;
305
  background-color: var(--fly-input-bg) !important;
306
+ padding: 0.75rem 1rem !important;
307
  line-height: 1.6;
308
  width: 100% !important;
309
  box-sizing: border-box !important;
310
+ font-family: var(--font-global) !important;
311
+ color: var(--fly-text-primary) !important;
312
+ transition: var(--transition-normal);
313
+ box-shadow: var(--shadow-sm);
314
  }}
315
  .gr-input > label + div > textarea:focus,
316
  .gr-dropdown > label + div > div > input:focus,
317
  .gr-dropdown > label + div > div > select:focus {{
318
  border-color: var(--fly-primary) !important;
319
+ box-shadow: 0 0 0 3px rgba(var(--fly-primary-rgb, 79, 70, 229), 0.3) !important; /* Assuming primary color has RGB equivalent */
320
  outline: none;
321
+ transform: translateY(-1px); /* Subtle lift effect */
322
  }}
323
+ /* Add this if you define --fly-primary-rgb for the focus shadow */
324
+ :root {{ --fly-primary-rgb: 79, 70, 229; }}
325
+
326
+
327
  .gr-dropdown svg.icon {{
328
  right: auto !important;
329
+ left: 1rem !important;
330
+ color: var(--fly-subtle-text) !important;
331
  }}
332
 
333
+ /* Output Textbox styling */
334
+ .output-text-container .gr-textbox[label*="ترجمه انگلیسی"] > label + div > textarea,
335
+ .output-text-container .gradio-interface .output_text .gr-textbox[data-testid="textbox"] > label + div > textarea {{
336
  background-color: var(--fly-panel-bg) !important;
337
+ border: 1px solid var(--fly-primary) !important; /* Accent border */
338
+ border-radius: var(--radius-md) !important;
339
+ min-height: 120px;
340
+ font-family: var(--font-english) !important;
341
+ font-size: 1.05em !important;
342
+ color: var(--fly-primary) !important;
343
  font-weight: 500;
344
+ padding: 0.75rem 1rem !important;
345
+ box-shadow: inset 0 1px 3px rgba(0,0,0,0.05);
346
+ }}
347
+ .output-audio-container .gr-audio {{ /* Style the audio player */
348
+ border-radius: var(--radius-md);
349
+ box-shadow: var(--shadow-sm);
350
+ }}
351
+ .output-audio-container .gr-audio audio {{
352
+ border-radius: var(--radius-md);
353
  }}
354
 
355
+
356
+ /* Accordion styling */
357
  .gr-accordion > button.gr-button {{
358
  font-weight: 600 !important;
359
+ padding: 0.75rem 1rem !important;
360
+ border-radius: var(--radius-md) !important;
361
+ background-color: #EDF2F7 !important; /* Lighter gray */
362
+ color: var(--fly-text-primary) !important;
363
+ border: 1px solid var(--fly-border-color) !important;
364
+ font-size: 0.95em;
365
  margin-bottom: 0.5rem;
366
+ transition: var(--transition-normal);
367
+ text-align: right; /* Ensure text is aligned right */
368
  }}
369
  .gr-accordion > button.gr-button:hover {{
370
+ background-color: #E2E8F0 !important;
371
+ border-color: var(--fly-primary) !important;
372
+ color: var(--fly-primary) !important;
373
  }}
374
  .gr-accordion > div.gr-panel {{
375
+ border-radius: var(--radius-md) !important;
376
+ border: 1px solid var(--fly-border-color) !important;
377
+ background-color: var(--fly-bg-white) !important;
378
+ padding: 1.25rem !important;
379
  box-shadow: none;
380
+ margin-top: -0.5rem; /* Pull up slightly under accordion button */
381
+ position: relative;
382
+ z-index: 5;
383
  }}
384
  .gr-slider {{
385
+ padding: 0.5rem 0 !important;
386
+ }}
387
+ .gr-slider input[type="range"]::-webkit-slider-thumb {{
388
+ background: var(--fly-primary) !important;
389
+ box-shadow: var(--shadow-sm) !important;
390
  }}
391
+ .gr-slider input[type="range"]::-moz-range-thumb {{
392
+ background: var(--fly-primary) !important;
393
+ box-shadow: var(--shadow-sm) !important;
394
  }}
395
 
396
+
397
+ /* Submit Button - More engaging */
398
+ .action-button-container .gr-button.gr-button-primary {{
399
  width: 100% !important;
400
+ padding: 0.85rem 1.5rem !important;
401
+ font-size: 1.05em !important;
402
+ font-weight: 700 !important;
403
+ border-radius: var(--radius-md) !important;
404
+ margin-top: 1.5rem !important;
 
405
  letter-spacing: 0.5px;
406
+ color: var(--fly-bg-white) !important;
407
+ background: linear-gradient(135deg, var(--fly-accent) 0%, #F97316 100%) !important; /* Amber to Orange gradient */
408
+ border: none !important;
409
+ box-shadow: var(--shadow-md), 0 0 15px rgba(var(--fly-accent-rgb, 245, 158, 11), 0.3); /* Accent glow */
410
+ transition: var(--transition-normal), transform 0.1s ease-out;
411
+ cursor: pointer;
412
  }}
413
+ /* Add this if you define --fly-accent-rgb for the button glow */
414
+ :root {{ --fly-accent-rgb: 245, 158, 11; }}
415
+
416
+ .action-button-container .gr-button.gr-button-primary:hover {{
417
+ transform: translateY(-3px) scale(1.02);
418
+ box-shadow: var(--shadow-lg), 0 0 25px rgba(var(--fly-accent-rgb, 245, 158, 11), 0.4);
419
  }}
420
+ .action-button-container .gr-button.gr-button-primary:active {{
421
+ transform: translateY(-1px) scale(0.98);
422
+ box-shadow: var(--shadow-sm), 0 0 10px rgba(var(--fly-accent-rgb, 245, 158, 11), 0.2);
423
+ }}
424
+
425
 
426
  /* Examples Section */
427
+ div#examples_section {{ /* Use specific ID */
428
+ margin-top: 2rem;
429
+ padding-top: 1.5rem;
430
+ border-top: 1px solid var(--fly-border-color);
431
  }}
432
+ div#examples_section > .gr-panel {{
433
  background-color: transparent !important;
434
  border: none !important;
435
  padding: 0 !important;
436
  box-shadow: none !important;
437
  }}
438
+ div#examples_section .gr-samples-header {{ /* Target Gradio 3.x/4.x header */
439
+ font-weight: 700;
440
+ color: var(--fly-text-primary);
441
+ font-size: 1.1em;
442
+ margin-bottom: 1rem;
443
+ text-align: right;
444
+ }}
445
+ div#examples_section .gr-sample-button, /* Gradio 4.x */
446
+ div#examples_section table tbody tr td button.gr-button-secondary /* Gradio 3.x (more specific) */ {{
447
+ background-color: var(--fly-input-bg) !important;
448
  color: var(--fly-primary) !important;
449
+ border-radius: var(--radius-md) !important;
450
+ font-size: 0.9em !important;
451
+ padding: 0.5rem 1rem !important;
452
+ border: 1px solid var(--fly-primary) !important;
453
+ transition: var(--transition-normal);
454
+ box-shadow: var(--shadow-sm);
455
+ font-weight: 500;
456
+ }}
457
+ div#examples_section .gr-sample-button:hover,
458
+ div#examples_section table tbody tr td button.gr-button-secondary:hover {{
459
+ background-color: var(--fly-primary) !important;
460
+ color: var(--fly-bg-white) !important;
461
+ transform: translateY(-2px);
462
+ box-shadow: var(--shadow-md);
 
463
  }}
464
 
465
+ /* Status Message */
466
+ .status-message-fly {{
467
+ padding: 0.75rem 1rem;
468
+ margin-top: 1.5rem;
469
+ border-radius: var(--radius-md);
470
+ font-weight: 500;
471
+ text-align: center;
472
+ font-size: 0.95em;
473
+ box-shadow: var(--shadow-sm);
474
+ }}
475
+ .status-message-fly.success {{
476
+ background-color: #D1FAE5; /* Emerald 100 */
477
+ color: #065F46; /* Emerald 700 */
478
+ border: 1px solid #A7F3D0; /* Emerald 200 */
479
+ }}
480
+ .status-message-fly.error {{
481
+ background-color: #FEE2E2; /* Red 100 */
482
+ color: #991B1B; /* Red 700 */
483
+ border: 1px solid #FECACA; /* Red 200 */
484
+ }}
485
+
486
+
487
  .custom-hr-fly {{
488
  height: 1px;
489
+ background-color: var(--fly-border-color);
490
+ margin: 2rem 0;
491
  border: none;
492
  }}
493
  .api-warning-message-fly {{
494
+ background-color: #FEF3C7 !important; /* Amber 100 */
495
+ color: #92400E !important; /* Amber 700 */
496
+ padding: 0.75rem 1rem !important;
497
+ border-radius: var(--radius-md) !important;
498
+ border: 1px solid #FDE68A !important; /* Amber 300 */
499
  text-align: center !important;
500
+ margin: 0 0.5rem 1.5rem 0.5rem !important;
501
  font-size: 0.9em !important;
502
  font-weight: 500;
503
+ box-shadow: var(--shadow-sm);
504
  }}
505
  .app-footer-fly {{
506
  text-align:center;
507
+ font-size:0.85em;
508
+ color: var(--fly-text-secondary);
509
+ margin-top:2.5rem;
510
+ padding: 1rem 0;
511
+ background-color: rgba(255,255,255,0.3); /* Subtle background for footer */
512
+ backdrop-filter: blur(5px); /* Glassmorphism hint */
513
+ border-top: 1px solid var(--fly-border-color);
514
  }}
515
  footer, .gradio-footer {{
516
  display: none !important;
517
  }}
518
 
519
+ /* Desktop improvements */
520
  @media (min-width: 768px) {{
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
521
  .main-content-columns-fly {{
522
  display: flex;
523
  flex-direction: row;
524
+ gap: 2rem;
525
+ align-items: flex-start; /* Align items to top */
526
  }}
527
  .main-content-columns-fly > .gr-column:nth-child(1) {{
528
  flex: 3;
529
  }}
530
  .main-content-columns-fly > .gr-column:nth-child(2) {{
531
  flex: 2;
532
+ position: sticky; /* Make output column sticky on scroll */
533
+ top: 1.5rem;
534
  }}
535
 
536
+ .action-button-container .gr-button.gr-button-primary {{
537
  width: auto !important;
538
+ min-width: 220px;
539
+ align-self: flex-end; /* Align button to the right in its container */
540
  }}
541
+ .app-title-card h1 {{ font-size: 2.75em !important; }}
542
+ .app-title-card p {{ font-size: 1.1em !important; }}
543
+ .content-panel {{ padding: 2rem; }}
 
 
 
 
 
 
 
 
 
 
 
 
544
  }}
545
  """
546
 
 
551
  elif default_english_tts_voice not in language_dict_persian_keys:
552
  default_english_tts_voice = list(language_dict_persian_keys.keys())[0] if language_dict_persian_keys else None
553
 
554
+ logging.info(f"Gradio version: {gr.__version__}")
555
+
556
  with gr.Blocks(theme=app_theme, css=custom_css, title="آموزش زبان فلای | Fly Language Learning") as demo:
557
+ gr.HTML(f"""
558
+ <div class="app-title-card">
559
+ <h1>🚀 Fly Language Learning</h1>
560
+ <p>جادوی ترجمه و تلفظ در دستان شما</p>
561
+ </div>
562
+ """)
563
 
564
  with gr.Column(elem_classes=["main-content-area-fly"]):
565
  if not GOOGLE_API_KEY or not model:
 
568
  elif not model: missing_key_msg += "خطا در بارگذاری مدل Gemini."
569
  gr.Markdown(f"<div class='api-warning-message-fly'>{missing_key_msg} بخش ترجمه غیرفعال خواهد بود. لطفاً تنظیمات Secrets را بررسی کنید.</div>")
570
 
571
+ with gr.Group(elem_classes=["content-panel"]):
572
  with gr.Row(elem_classes=["main-content-columns-fly"]):
573
+ with gr.Column(scale=3, elem_id="input_column"):
574
  input_text_persian = gr.Textbox(
575
+ lines=5, label="📝 متن فارسی خود را بنویسید:",
576
+ placeholder="مثال: جهان شگفت‌انگیز زبان‌ها را کاوش کنید...",
577
  elem_id="persian_input_textbox"
578
  )
579
  language_dropdown_tts_english = gr.Dropdown(
580
  choices=list(language_dict_persian_keys.keys()) if language_dict_persian_keys else ["لیست خالی"],
581
  value=default_english_tts_voice if default_english_tts_voice else "لیست خالی",
582
+ label="🗣️ گوینده و لهجه انگلیسی:",
583
  interactive=bool(language_dict_persian_keys and default_english_tts_voice is not None)
584
  )
585
+ with gr.Accordion("🎧 تنظیمات پیشرفته صدا", open=False):
586
+ rate_slider = gr.Slider(minimum=-100, maximum=100, value=0, step=10, label="سرعت گفتار")
587
+ volume_slider = gr.Slider(minimum=-100, maximum=100, value=0, step=10, label="بلندی صدا")
588
+ pitch_slider = gr.Slider(minimum=-50, maximum=50, value=0, step=5, label="زیر و بمی صدا")
 
589
 
590
+ with gr.Column(elem_classes=["action-button-container"]):
591
+ # Using variant="primary" for Gradio theming, CSS will override for advanced style
592
+ submit_button = gr.Button("ترجمه و پخش جادویی ✨", variant="primary")
593
+
594
+ with gr.Column(scale=2, elem_id="output_column", elem_classes=["result-area-fly"]):
595
+ with gr.Group(elem_classes=["output-text-container"]):
596
+ output_text_translated = gr.Textbox(
597
+ label="📜 ترجمه انگلیسی روان:", interactive=False, lines=6,
598
+ placeholder="ترجمه در اینجا پدیدار خواهد شد...",
599
+ elem_id="english_output_textbox"
600
+ )
601
+ with gr.Group(elem_classes=["output-audio-container"]):
602
+ output_audio = gr.Audio(label="🎤 بشنوید:", type="filepath", format="mp3", interactive=False)
603
+
604
+ status_message = gr.Markdown(visible=False, elem_classes=["status-message-fly"])
605
+
606
 
607
  if language_dict_persian_keys and default_english_tts_voice:
608
+ gr.HTML("<hr class='custom-hr-fly'>") # Visual separator
609
  num_voices = len(language_dict_persian_keys)
610
  voice_keys = list(language_dict_persian_keys.keys())
611
 
 
630
  gr.Examples(
631
  examples=valid_example_list,
632
  inputs=[input_text_persian, language_dropdown_tts_english, rate_slider, volume_slider, pitch_slider],
633
+ # outputs should match the number of return values from the function
634
+ outputs=[output_text_translated, output_audio, status_message],
635
  fn=translate_and_speak_sync_wrapper,
636
+ label="🌟 نمونه‌های الهام‌بخش (برای امتحان کلیک کنید):",
637
+ elem_id="examples_section" # Ensure this ID is targeted in CSS
638
  )
639
 
640
+ gr.Markdown("<p class='app-footer-fly'>ساخته شده با ❤️ و توسط تیم فلای | Fly Language Learning © ۲۰۲۴</p>")
641
 
 
 
642
  if 'submit_button' in locals() and submit_button is not None:
643
+ def handle_submit_click(persian_text, english_tts_voice_key, rate, volume, pitch):
644
+ translated_text, audio_path, status_update = translate_and_speak_sync_wrapper(
645
+ persian_text, english_tts_voice_key, rate, volume, pitch
646
+ )
647
+ # Determine status message class based on content
648
+ status_class = "status-message-fly"
649
+ if status_update and isinstance(status_update, gr. uomini.Update): # Check if it's a Gradio Update object
650
+ current_value = status_update.value
651
+ if current_value: # Check if value is not None or empty
652
+ if "موفقیت" in current_value or "✅" in current_value:
653
+ status_class += " success"
654
+ elif "خطا" in current_value or "⚠️" in current_value or "🚫" in current_value:
655
+ status_class += " error"
656
+
657
+ # Update status_message with new class
658
+ updated_status_message = gr.update(value=status_update.value if status_update else None,
659
+ visible=bool(status_update and status_update.value),
660
+ elem_classes=[status_class]) # Pass as a list
661
+
662
+ return translated_text, audio_path, updated_status_message
663
+
664
  submit_button.click(
665
+ fn=handle_submit_click, # Use the new handler
666
  inputs=[input_text_persian, language_dropdown_tts_english, rate_slider, volume_slider, pitch_slider],
667
+ outputs=[output_text_translated, output_audio, status_message] # Match the number of outputs
668
  )
669
  else:
670
  logging.error("Submit button was not initialized correctly or is None.")
 
672
 
673
  if __name__ == "__main__":
674
  if not language_dict_persian_keys:
675
+ logging.critical("CRITICAL: Voice dictionary (language_dict_persian_keys) is empty!")
676
  elif not default_english_tts_voice:
677
+ logging.warning("WARNING: No default English TTS voice could be set.")
678
 
679
+ demo.launch(server_name="0.0.0.0", server_port=7860, debug=True, show_error=True)