Hamed744 commited on
Commit
bbf0e20
·
verified ·
1 Parent(s): 1db2754

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +115 -68
app.py CHANGED
@@ -3,27 +3,42 @@ import edge_tts
3
  import tempfile
4
  import asyncio
5
  import traceback
6
- import threading # دیگر برای _get_or_create_event_loop استفاده نمی‌شود، اما ممکن است کتابخانه‌های دیگر از آن استفاده کنند
7
  import os
8
  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
-
13
- GOOGLE_API_KEY = os.environ.get('GOOGLE_API_KEY')
14
- model = None
15
- if GOOGLE_API_KEY:
16
- try:
17
- genai.configure(api_key=GOOGLE_API_KEY)
18
- model = genai.GenerativeModel('gemini-1.5-flash-latest')
19
- logging.info("سرویس Gemini با موفقیت پیکربندی شد.")
20
- except Exception as e:
21
- logging.error(f"خطا در پیکربندی Gemini: {e}. قابلیت ترجمه غیرفعال خواهد بود.")
22
- model = None
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  else:
24
- logging.warning("هشدار: کلید API گوگل (GOOGLE_API_KEY) تنظیم نشده است. قابلیت ترجمه غیرفعال خواهد بود.")
 
 
25
 
26
- # --- دیکشنری صداهای انگلیسی با حذف موارد مشخص شده ---
27
  language_dict_persian_keys = {
28
  'انگلیسی (آمریکا) - جنی (زن)': 'en-US-JennyNeural',
29
  'انگلیسی (آمریکا) - گای (مرد)': 'en-US-GuyNeural',
@@ -65,19 +80,48 @@ language_dict_persian_keys = {
65
  }
66
 
67
  async def translate_text_gemini(text, target_language="English"):
68
- if not model: return "خطا: سرویس ترجمه در دسترس نیست (کلید API یا مدل مشکل دارد).", None
69
- if not text or not text.strip(): return "خطا: متنی برای ترجمه وارد نشده است.", None
70
- try:
71
- 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}:"
72
- # این تابع باید در یک event loop که توسط Gradio مدیریت می‌شود اجرا شود
73
- response = await model.generate_content_async(prompt)
74
- translated_text = response.text.strip()
75
- if translated_text.lower().startswith(f"{target_language.lower()}:"):
76
- translated_text = translated_text[len(target_language)+1:].strip()
77
- return "ترجمه موفق", translated_text
78
- except Exception as e:
79
- logging.error(f"خطای Gemini: {e}\n{traceback.format_exc()}")
80
- return f"خطای ترجمه: {type(e).__name__}", None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
81
 
82
  MAX_TTS_RETRIES = 1
83
  TTS_RETRY_DELAY = 0.5
@@ -119,21 +163,24 @@ async def text_to_speech_edge_async(text_to_speak, tts_voice_key, rate, volume,
119
 
120
  return "خطای TTS: تلاش‌ها برای تولید صدا ناموفق بود.", None
121
 
122
- # تابع _get_or_create_event_loop و _event_loops_by_thread حذف شد.
123
 
124
- async def translate_and_speak_async_wrapper(persian_text, english_tts_voice_key, rate, volume, pitch): # تابع async شد
125
  error_message_for_ui = ""
126
 
127
- if not GOOGLE_API_KEY or not model:
128
- error_message_for_ui = "خطا: سرویس ترجمه پیکربندی نشده است. لطفاً از تنظیم صحیح GOOGLE_API_KEY در Secrets اطمینان حاصل کنید."
129
- return error_message_for_ui, None
 
 
 
 
 
130
  if not persian_text or not persian_text.strip():
131
  error_message_for_ui = "لطفاً متن فارسی را برای ترجمه وارد کنید."
132
  return error_message_for_ui, None
133
 
134
- # فراخوانی مستقیم با await
135
  translation_status_msg, translated_text = await translate_text_gemini(persian_text, target_language="English")
136
- translated_text_output = translated_text if translated_text else "ترجمه ناموفق بود."
137
 
138
  if "خطا" in translation_status_msg or not translated_text:
139
  error_message_for_ui = f"{translated_text_output}\n({translation_status_msg})"
@@ -148,7 +195,6 @@ async def translate_and_speak_async_wrapper(persian_text, english_tts_voice_key,
148
  error_message_for_ui = f"{translated_text_output}\n\n(خطای TTS: هیچ صدایی برای انتخاب موجود نیست.)"
149
  return error_message_for_ui, None
150
 
151
- # فراخوانی مستقیم با await
152
  tts_status_msg, audio_path = await text_to_speech_edge_async(translated_text, english_tts_voice_key, rate, volume, pitch)
153
 
154
  if "خطا" in tts_status_msg or not audio_path:
@@ -157,17 +203,14 @@ async def translate_and_speak_async_wrapper(persian_text, english_tts_voice_key,
157
 
158
  return translated_text_output, audio_path
159
 
 
160
  FLY_PRIMARY_COLOR_HEX = "#4F46E5"
161
  FLY_SECONDARY_COLOR_HEX = "#10B981"
162
- FLY_ACCENT_COLOR_HEX = "#D97706"
163
- FLY_TEXT_COLOR_HEX = "#1F2937"
164
- FLY_SUBTLE_TEXT_HEX = "#6B7280"
165
- FLY_LIGHT_BACKGROUND_HEX = "#F9FAFB"
166
- FLY_WHITE_HEX = "#FFFFFF"
167
- FLY_BORDER_COLOR_HEX = "#D1D5DB"
168
  FLY_INPUT_BG_HEX_SIMPLE = "#F3F4F6"
169
  FLY_PANEL_BG_SIMPLE = "#E0F2FE"
170
 
 
171
  app_theme_outer = gr.themes.Base(
172
  font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
173
  ).set(
@@ -241,10 +284,14 @@ with gr.Blocks(theme=app_theme_outer, css=custom_css, title="آموزش زبان
241
 
242
  with gr.Column(elem_classes=["main-content-area"]):
243
  with gr.Group(elem_classes=["content-panel-simple"]):
244
- if not GOOGLE_API_KEY or not model:
245
- missing_key_msg = "⚠️ هشدار: قابلیت ترجمه غیرفعال است. "
246
- if not GOOGLE_API_KEY: missing_key_msg += "لطفاً کلید GOOGLE_API_KEY را در بخش Secrets این Space تنظیم کنید."
247
- elif not model: missing_key_msg += "خطا در بارگذاری مدل Gemini. لطفاً لاگ‌ها یا صحت کلید API را بررسی کنید."
 
 
 
 
248
  gr.Markdown(f"<div class='api-warning-message'>{missing_key_msg}</div>")
249
 
250
  with gr.Row(elem_classes=["main-content-row"]):
@@ -288,31 +335,34 @@ with gr.Blocks(theme=app_theme_outer, css=custom_css, title="آموزش زبان
288
  label="🎧 فایل صوتی",
289
  format="mp3",
290
  interactive=False,
291
- autoplay=True
292
  )
293
 
294
  if language_dict_persian_keys and default_english_tts_voice and default_english_tts_voice != "لیست صداها خالی است":
295
  gr.HTML("<hr class='custom-hr'>")
296
  num_voices = len(language_dict_persian_keys)
297
  voice_keys = list(language_dict_persian_keys.keys())
 
298
  voice1_idx = 0
299
- voice2_idx = min(1, num_voices - 1) if num_voices > 1 else 0
300
- voice3_idx = min(2, num_voices - 1) if num_voices > 2 else 0
301
-
302
  example_list = [
303
- ["قیمت این لباس چقدر است؟", voice_keys[voice1_idx], 0, 0, 0],
304
- ["می‌توانید آدرس را روی نقشه به من نشان دهید؟", voice_keys[voice2_idx], 0, 0, 0],
305
- ["ببخشید، متوجه نشدم. امکان دارد تکرار کنید؟", voice_keys[voice3_idx], -10, 0, 0],
306
  ]
307
-
308
- gr.Examples(
309
- examples=example_list,
310
- inputs=[input_text_persian, language_dropdown_tts_english, rate_slider, volume_slider, pitch_slider],
311
- outputs=[output_text_translated, output_audio],
312
- fn=translate_and_speak_async_wrapper, # استفاده از تابع async wrapper
313
- cache_examples=os.getenv("GRADIO_CACHE_EXAMPLES", "False").lower() == "true",
314
- label="💡 نمونه‌های کاربردی"
315
- )
 
 
316
  else:
317
  gr.Markdown("<p style='text-align:center; color:var(--fly-text-secondary); margin-top:1rem;'>نمونه‌ای برای نمایش موجود نیست (لیست صداها خالی است یا صدای پیش‌فرض معتبر نیست).</p>")
318
 
@@ -320,23 +370,20 @@ with gr.Blocks(theme=app_theme_outer, css=custom_css, title="آموزش زبان
320
 
321
  if 'submit_button' in locals() and submit_button is not None:
322
  submit_button.click(
323
- fn=translate_and_speak_async_wrapper, # استفاده از تابع async wrapper
324
  inputs=[input_text_persian, language_dropdown_tts_english, rate_slider, volume_slider, pitch_slider],
325
  outputs=[output_text_translated, output_audio]
326
  )
327
  else:
328
- logging.error("Submit button was not initialized correctly or is None.")
329
 
330
  if __name__ == "__main__":
331
  if not language_dict_persian_keys:
332
  logging.critical("خطای بحرانی: language_dict_persian_keys خالی است!")
333
 
334
- # اگر از داخل یک environment مثل Hugging Face Spaces اجرا می‌کنید که خودش event loop را مدیریت می‌کند،
335
- # نیازی به asyncio.run(demo.launch(...)) نیست. demo.launch() به تنها کافی است.
336
- # Gradio خودش با توابع async به درستی کار می‌کند.
337
  demo.launch(
338
  server_name="0.0.0.0",
339
- server_port=7860,
340
  debug=os.environ.get("GRADIO_DEBUG", "False").lower() == "true",
341
  show_error=True
342
- )
 
3
  import tempfile
4
  import asyncio
5
  import traceback
 
6
  import os
7
  import google.generativeai as genai
8
  import logging
9
 
10
+ # --- START: پیکربندی لاگینگ ---
11
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(threadName)s - %(message)s')
12
+ # --- END: پیکربندی لاگینگ ---
13
+
14
+ # --- START: منطق چرخش API Key برای Gemini ---
15
+ API_KEYS_GEMINI = []
16
+ i = 1
17
+ while True:
18
+ # خواندن کلیدها با فرمت GEMINI_API_KEY_1, GEMINI_API_KEY_2, ...
19
+ key = os.environ.get(f'GEMINI_API_KEY_{i}')
20
+ if key:
21
+ API_KEYS_GEMINI.append(key)
22
+ i += 1
23
+ else:
24
+ break
25
+
26
+ NUM_GEMINI_KEYS = len(API_KEYS_GEMINI)
27
+ current_gemini_key_index = 0
28
+ # قفل برای همگام‌سازی دسترسی به current_gemini_key_index و پیکربندی genai
29
+ gemini_operations_lock = asyncio.Lock()
30
+
31
+ if NUM_GEMINI_KEYS == 0:
32
+ logging.error(
33
+ 'خطای حیاتی: هیچ Secret با نام GEMINI_API_KEY_n (مثلاً GEMINI_API_KEY_1) یافت نشد! ' +
34
+ 'قابلیت ترجمه غیرفعال خواهد بود. لطفاً Secret ها را در تنظیمات Space خود اضافه کنید.'
35
+ )
36
  else:
37
+ logging.info(f"تعداد {NUM_GEMINI_KEYS} کلید API جیمینای بارگذاری شد.")
38
+ # --- END: منطق چرخش API Key برای Gemini ---
39
+
40
 
41
+ # --- دیکشنری صداهای انگلیسی (بدون تغییر) ---
42
  language_dict_persian_keys = {
43
  'انگلیسی (آمریکا) - جنی (زن)': 'en-US-JennyNeural',
44
  'انگلیسی (آمریکا) - گای (مرد)': 'en-US-GuyNeural',
 
80
  }
81
 
82
  async def translate_text_gemini(text, target_language="English"):
83
+ if NUM_GEMINI_KEYS == 0:
84
+ return "خطا: سرویس ترجمه پیکربندی نشده (هیچ کلید API جیمینای معتبری یافت نشد).", None
85
+ if not text or not text.strip():
86
+ return "خطا: متنی برای ترجمه وارد نشده است.", None
87
+
88
+ selected_api_key = None
89
+ model_to_use = 'gemini-1.5-flash-latest' # یا هر مدلی که نیاز دارید
90
+
91
+ async with gemini_operations_lock:
92
+ global current_gemini_key_index # برای تغییر ایندکس سراسری
93
+
94
+ # انتخاب کلید بعدی به صورت چرخشی
95
+ key_index_to_use = current_gemini_key_index
96
+ selected_api_key = API_KEYS_GEMINI[key_index_to_use]
97
+ current_gemini_key_index = (current_gemini_key_index + 1) % NUM_GEMINI_KEYS
98
+
99
+ logging.info(f"قفل Gemini گرفته شد. استفاده از کلید API با اندیس: {key_index_to_use} (...{selected_api_key[-4:]}) برای مدل {model_to_use}")
100
+
101
+ try:
102
+ # پیکربندی Gemini با کلید انتخاب شده برای این درخواست
103
+ genai.configure(api_key=selected_api_key)
104
+ # ایجاد نمونه مدل با پیکربندی فعلی
105
+ model_instance = genai.GenerativeModel(model_to_use)
106
+
107
+ logging.info(f"پیکربندی Gemini با کلید ...{selected_api_key[-4:]} برای مدل {model_to_use} انجام شد.")
108
+
109
+ 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}:"
110
+
111
+ response = await model_instance.generate_content_async(prompt)
112
+ translated_text = response.text.strip()
113
+
114
+ if translated_text.lower().startswith(f"{target_language.lower()}:"):
115
+ translated_text = translated_text[len(target_language)+1:].strip()
116
+
117
+ logging.info(f"ترجمه با کلید ...{selected_api_key[-4:]} موفق. قفل Gemini آزاد می‌شود.")
118
+ return "ترجمه موفق", translated_text
119
+
120
+ except Exception as e:
121
+ key_info = f"...{selected_api_key[-4:]}" if selected_api_key else "N/A"
122
+ logging.error(f"خطای Gemini با کلید {key_info} (مدل {model_to_use}): {e}\n{traceback.format_exc()}")
123
+ logging.info(f"قفل Gemini برای کلید {key_info} (پس از خطا) آزاد می‌شود.") # async with خودش مدیریت می‌کند
124
+ return f"خطای ترجمه ({type(e).__name__}) با کلید جاری.", None
125
 
126
  MAX_TTS_RETRIES = 1
127
  TTS_RETRY_DELAY = 0.5
 
163
 
164
  return "خطای TTS: تلاش‌ها برای تولید صدا ناموفق بود.", None
165
 
 
166
 
167
+ async def translate_and_speak_async_wrapper(persian_text, english_tts_voice_key, rate, volume, pitch):
168
  error_message_for_ui = ""
169
 
170
+ if NUM_GEMINI_KEYS == 0: # بررسی اولیه وجود کلیدهای Gemini
171
+ error_message_for_ui = (
172
+ "خطا: سرویس ترجمه پیکربندی نشده است. "
173
+ "هیچ کلید API جیمینای (GEMINI_API_KEY_n) در بخش Secrets این Space یافت نشد. "
174
+ "لطفاً حداقل یک کلید با نام GEMINI_API_KEY_1 تنظیم کنید."
175
+ )
176
+ return error_message_for_ui, None # برگرداندن None برای صوت
177
+
178
  if not persian_text or not persian_text.strip():
179
  error_message_for_ui = "لطفاً متن فارسی را برای ترجمه وارد کنید."
180
  return error_message_for_ui, None
181
 
 
182
  translation_status_msg, translated_text = await translate_text_gemini(persian_text, target_language="English")
183
+ translated_text_output = translated_text if translated_text else "ترجمه ناموفق بود یا خطایی رخ داد."
184
 
185
  if "خطا" in translation_status_msg or not translated_text:
186
  error_message_for_ui = f"{translated_text_output}\n({translation_status_msg})"
 
195
  error_message_for_ui = f"{translated_text_output}\n\n(خطای TTS: هیچ صدایی برای انتخاب موجود نیست.)"
196
  return error_message_for_ui, None
197
 
 
198
  tts_status_msg, audio_path = await text_to_speech_edge_async(translated_text, english_tts_voice_key, rate, volume, pitch)
199
 
200
  if "خطا" in tts_status_msg or not audio_path:
 
203
 
204
  return translated_text_output, audio_path
205
 
206
+ # --- بخش UI و Gradio (بدون تغییر عمده به جز پیام هشدار کلید) ---
207
  FLY_PRIMARY_COLOR_HEX = "#4F46E5"
208
  FLY_SECONDARY_COLOR_HEX = "#10B981"
209
+ # ... (بقیه متغیرهای CSS شما بدون تغییر) ...
 
 
 
 
 
210
  FLY_INPUT_BG_HEX_SIMPLE = "#F3F4F6"
211
  FLY_PANEL_BG_SIMPLE = "#E0F2FE"
212
 
213
+
214
  app_theme_outer = gr.themes.Base(
215
  font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
216
  ).set(
 
284
 
285
  with gr.Column(elem_classes=["main-content-area"]):
286
  with gr.Group(elem_classes=["content-panel-simple"]):
287
+ # نمایش هشدار اگر هیچ کلید API جیمینای تنظیم نشده باشد
288
+ if NUM_GEMINI_KEYS == 0:
289
+ missing_key_msg = (
290
+ "⚠️ هشدار: قابلیت ترجمه غیرفعال است. "
291
+ "هیچ کلید API جیمینای (با فرمت GEMINI_API_KEY_1, GEMINI_API_KEY_2, ...) "
292
+ "در بخش Secrets این Space یافت نشد. "
293
+ "لطفاً حداقل یک کلید با نام GEMINI_API_KEY_1 تنظیم کنید."
294
+ )
295
  gr.Markdown(f"<div class='api-warning-message'>{missing_key_msg}</div>")
296
 
297
  with gr.Row(elem_classes=["main-content-row"]):
 
335
  label="🎧 فایل صوتی",
336
  format="mp3",
337
  interactive=False,
338
+ autoplay=True # در برخی مرورگرها ممکن است کار نکند
339
  )
340
 
341
  if language_dict_persian_keys and default_english_tts_voice and default_english_tts_voice != "لیست صداها خالی است":
342
  gr.HTML("<hr class='custom-hr'>")
343
  num_voices = len(language_dict_persian_keys)
344
  voice_keys = list(language_dict_persian_keys.keys())
345
+ # انتخاب صداهای نمونه به شکلی که از ایندکس خارج نشود
346
  voice1_idx = 0
347
+ voice2_idx = min(1, num_voices - 1) if num_voices > 0 else 0
348
+ voice3_idx = min(2, num_voices - 1) if num_voices > 0 else 0
349
+
350
  example_list = [
351
+ ["قیمت این لباس چقدر است؟", voice_keys[voice1_idx] if num_voices > 0 else current_default_voice, 0, 0, 0],
352
+ ["می‌توانید آدرس را روی نقشه به من نشان دهید؟", voice_keys[voice2_idx] if num_voices > 0 else current_default_voice, 0, 0, 0],
353
+ ["ببخشید، متوجه نشدم. امکان دارد تکرار کنید؟", voice_keys[voice3_idx] if num_voices > 0 else current_default_voice, -10, 0, 0],
354
  ]
355
+ if num_voices == 0: example_list = [] # اگر صدایی نیست، مثال خالی باشد
356
+
357
+ if example_list:
358
+ gr.Examples(
359
+ examples=example_list,
360
+ inputs=[input_text_persian, language_dropdown_tts_english, rate_slider, volume_slider, pitch_slider],
361
+ outputs=[output_text_translated, output_audio],
362
+ fn=translate_and_speak_async_wrapper,
363
+ cache_examples=os.getenv("GRADIO_CACHE_EXAMPLES", "False").lower() == "true",
364
+ label="💡 نمونه‌های کاربردی"
365
+ )
366
  else:
367
  gr.Markdown("<p style='text-align:center; color:var(--fly-text-secondary); margin-top:1rem;'>نمونه‌ای برای نمایش موجود نیست (لیست صداها خالی است یا صدای پیش‌فرض معتبر نیست).</p>")
368
 
 
370
 
371
  if 'submit_button' in locals() and submit_button is not None:
372
  submit_button.click(
373
+ fn=translate_and_speak_async_wrapper,
374
  inputs=[input_text_persian, language_dropdown_tts_english, rate_slider, volume_slider, pitch_slider],
375
  outputs=[output_text_translated, output_audio]
376
  )
377
  else:
378
+ logging.error("دکمه ارسال (Submit button) به درستی مقداردهی اولیه نشده است.")
379
 
380
  if __name__ == "__main__":
381
  if not language_dict_persian_keys:
382
  logging.critical("خطای بحرانی: language_dict_persian_keys خالی است!")
383
 
 
 
 
384
  demo.launch(
385
  server_name="0.0.0.0",
386
+ server_port=int(os.getenv("PORT", 7860)), # استفاده از متغیر PORT اگر تنظیم شده باشد
387
  debug=os.environ.get("GRADIO_DEBUG", "False").lower() == "true",
388
  show_error=True
389
+ )