rick commited on
Commit
3757896
·
unverified ·
1 Parent(s): fb31e98

update app settings

Browse files
Files changed (2) hide show
  1. app.py +49 -755
  2. pages/main.py +818 -0
app.py CHANGED
@@ -15,24 +15,37 @@ from typing import Optional
15
  from typing import Tuple
16
  from typing import Union
17
 
18
-
19
  # Third-party libraries
20
- import requests
21
  import streamlit as st
22
- from audiorecorder import audiorecorder
23
- from openai import OpenAI
24
- from pydub import AudioSegment
25
 
26
 
27
  __version__ = "1.2.0"
28
 
29
  # Au début du fichier, après les imports
30
- st.set_page_config(
31
- page_title=f"DEMORRHA - (v{__version__})",
32
- page_icon="👹",
33
- layout="wide",
34
- initial_sidebar_state="collapsed"
35
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
 
37
  def load_ui_language(file_path: Optional[str] = "ui_lang_support.json") -> Dict[str, Any]:
38
  """
@@ -57,8 +70,6 @@ def load_ui_language(file_path: Optional[str] = "ui_lang_support.json") -> Dict[
57
  print(f"{get_translation('erreur_lecture_fichier')} {e}")
58
  return {}
59
 
60
- # Dictionary to store translations
61
- translations = load_ui_language()
62
 
63
  def get_translation(key: str) -> str:
64
  """
@@ -70,757 +81,40 @@ def get_translation(key: str) -> str:
70
  Returns:
71
  str: Le texte traduit.
72
  """
73
- return translations[st.session_state.interface_language][key]
74
-
75
- # OpenAI client configuration with API key
76
- client = OpenAI(api_key=getenv("OPENAI_API_KEY"))
77
-
78
- def read_file(file_name: str) -> str:
79
- """
80
- Lit et retourne le contenu des fichiers texte.
81
-
82
- Args:
83
- file_name (str): Le nom du fichier à lire.
84
-
85
- Returns:
86
- str: Le contenu du fichier ou un message d'erreur.
87
- """
88
- try:
89
- with open(file_name, 'r', encoding='utf-8') as file:
90
- content = file.read()
91
- return content
92
- except FileNotFoundError:
93
- return f"{get_translation('erreur_fichier_non_trouve')} {file_name}"
94
- except IOError as e:
95
- return f"{get_translation('erreur_lecture_fichier')} {str(e)}"
96
-
97
- def split_audio(audio_file: str, max_size_mb: int = 25) -> List[str]:
98
- """
99
- Divise un fichier audio en segments de 25 Mo ou moins.
100
-
101
- Args:
102
- audio_file (str): Chemin vers le fichier audio.
103
- max_size_mb (int): Taille maximale de chaque segment en Mo.
104
-
105
- Returns:
106
- List[str]: Liste des chemins vers les segments audio divisés.
107
- """
108
- try:
109
- audio = AudioSegment.from_wav(audio_file)
110
- duration_ms = len(audio)
111
- segment_duration_ms = int(
112
- (max_size_mb * 1024 * 1024 * 8) /
113
- (audio.frame_rate * audio.sample_width * audio.channels)
114
- )
115
-
116
- segments = []
117
- for start in range(0, duration_ms, segment_duration_ms):
118
- end = min(start + segment_duration_ms, duration_ms)
119
- segment = audio[start:end]
120
- with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_segment:
121
- segment.export(temp_segment.name, format="wav")
122
- segments.append(temp_segment.name)
123
-
124
- return segments
125
- except IOError as e:
126
- print(f"Erreur lors de la lecture ou de l'écriture du fichier audio : {e}")
127
- return []
128
- except ValueError as e:
129
- print(f"Erreur de valeur lors du traitement de l'audio : {e}")
130
- return []
131
-
132
- # Fonction modifiée pour transcrire l'audio en texte
133
- def transcribe_audio(audio_file: IO, language: Optional[str] = None) -> str:
134
- """
135
- Transcrit un fichier audio en texte.
136
-
137
- Args:
138
- audio_file (IO): Le fichier audio à transcrire.
139
- language (Optional[str]): La langue de l'audio. Par défaut None.
140
-
141
- Returns:
142
- str: Le texte transcrit.
143
- """
144
- max_size_mb = 25
145
- file_size_mb = os.path.getsize(audio_file.name) / (1024 * 1024)
146
-
147
- try:
148
- with st.status("Transcription de l'audio en cours...") as status:
149
- if file_size_mb > max_size_mb:
150
- status.update(label="Découpage de l'audio en segments...")
151
- segments = split_audio(audio_file.name, max_size_mb)
152
- full_transcript = ""
153
- for i, segment in enumerate(segments):
154
- status.update(label=f"Transcription du segment {i+1}/{len(segments)}...")
155
- with open(segment, "rb") as audio_segment:
156
- transcript = client.audio.transcriptions.create(
157
- model="whisper-1",
158
- file=audio_segment,
159
- language=language
160
- )
161
- full_transcript += f"{transcript.text} "
162
- os.unlink(segment) # Supprime le fichier temporaire
163
- status.update(label="Transcription terminée", state="complete")
164
- return full_transcript.strip()
165
- else:
166
- status.update(label="Transcription de l'audio...")
167
- with open(audio_file.name, "rb") as audio_file:
168
- transcript = client.audio.transcriptions.create(
169
- model="whisper-1",
170
- file=audio_file,
171
- language=language
172
- )
173
- status.update(label="Transcription terminée", state="complete")
174
- return transcript.text
175
- except IOError as e:
176
- st.error(f"Erreur d'entrée/sortie lors de la transcription : {e}")
177
- return ""
178
- except Exception as e:
179
- st.error(f"Erreur lors de la transcription : {e}")
180
- return ""
181
-
182
- # Fonction pour détecter la langue d'un texte donné
183
- def detect_language(input_text: str, temperature: float = 0.01) -> str:
184
- """
185
- Détecte la langue d'un texte donné.
186
-
187
- Args:
188
- input_text (str): Le texte dont il faut détecter la langue.
189
- temperature (float): La température pour le modèle de langage. Par défaut à 0.01.
190
-
191
- Returns:
192
- str: La langue détectée au format ISO-639-1.
193
-
194
- Raises:
195
- ValueError: Si la réponse de l'API est invalide.
196
- requests.RequestException: En cas d'erreur de communication avec l'API.
197
- """
198
- system_prompt = (
199
- "Agissez comme une fonction de détection de langue. "
200
- "Je fournirai du texte dans n'importe quelle langue, et vous détecterez sa langue. "
201
- "Fournissez le résultat de votre détection au format ISO-639-1. "
202
- "Votre réponse doit représenter l'argument `language` et ne contenir "
203
- "que sa valeur sous forme de chaîne. "
204
- "Fournir la langue d'entrée au format ISO-639-1 améliorera la précision et la latence."
205
- )
206
- try:
207
- response = client.chat.completions.create(
208
- model="gpt-4o-mini",
209
- temperature=temperature,
210
- messages=[
211
- {
212
- "role": "system",
213
- "content": system_prompt
214
- },
215
- {
216
- "role": "user",
217
- "content": input_text
218
- }
219
- ]
220
- )
221
- detected_language = response.choices[0].message.content
222
- if not detected_language:
223
- raise ValueError("La réponse de l'API est vide")
224
- return detected_language
225
- except requests.RequestException as e:
226
- raise requests.RequestException(f"Erreur de communication avec l'API : {str(e)}")
227
- except Exception as e:
228
- raise ValueError(f"Erreur inattendue lors de la détection de la langue : {str(e)}")
229
-
230
- def get_duration_pydub(audio_file: str) -> float:
231
- """
232
- Obtient la durée d'un fichier audio en utilisant pydub.
233
-
234
- Args:
235
- audio_file (str): Chemin vers le fichier audio.
236
-
237
- Returns:
238
- float: Durée du fichier audio en secondes.
239
- """
240
- try:
241
- audio = AudioSegment.from_file(audio_file)
242
- return audio.duration_seconds
243
- except FileNotFoundError:
244
- print(f"Erreur : Le fichier audio '{audio_file}' n'a pas été trouvé.")
245
- return 0.0
246
- except Exception as e:
247
- print(f"Erreur lors de la lecture du fichier audio : {str(e)}")
248
- return 0.0
249
-
250
- def text_to_speech(text: str) -> Tuple[Optional[bytes], float]:
251
- """
252
- Convertit du texte en parole en utilisant l'API OpenAI.
253
-
254
- Args:
255
- text (str): Le texte à convertir en parole.
256
-
257
- Returns:
258
- Tuple[Optional[bytes], float]: Un tuple contenant les octets audio et la durée de l'audio en secondes.
259
- """
260
- try:
261
- response = client.audio.speech.create(
262
- model="tts-1",
263
- voice=st.session_state.tts_voice,
264
- input=text
265
- )
266
-
267
- # Sauvegarde l'audio dans un fichier temporaire
268
- with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio:
269
- response.stream_to_file(temp_audio.name)
270
-
271
- # Lit le contenu du fichier audio
272
- with open(temp_audio.name, "rb") as audio_file:
273
- audio_bytes = audio_file.read()
274
-
275
- # Obtient la durée de l'audio en secondes
276
- audio_duration = get_duration_pydub(temp_audio.name)
277
-
278
- return audio_bytes, audio_duration
279
- except Exception as e:
280
- print(f"Erreur lors de la conversion texte-parole : {str(e)}")
281
- return None, 0.0
282
-
283
- def concatenate_audio_files(audio_list: List[Tuple[bytes, float]]) -> Optional[bytes]:
284
- """
285
- Concatène plusieurs fichiers audio avec des effets sonores.
286
-
287
- Args:
288
- audio_list (List[Tuple[bytes, float]]): Une liste de tuples, chacun contenant
289
- des octets audio et la durée.
290
-
291
- Returns:
292
- Optional[bytes]: L'audio concaténé sous forme d'octets, ou None en cas d'erreur.
293
- """
294
- # Créer un segment audio vide
295
- final_audio = AudioSegment.empty()
296
-
297
- try:
298
- # Charger les effets sonores
299
- begin_sound = AudioSegment.from_mp3(
300
- "sound-effects/voice-message-play-begin/voice-message-play-begin-1.mp3"
301
- )
302
- end_sound = AudioSegment.from_mp3(
303
- "sound-effects/voice-message-play-ending/voice-message-play-ending-1.mp3"
304
- )
305
-
306
- # 5 secondes de silence
307
- silence = AudioSegment.silent(duration=1500) # 1500 ms = 1.5 secondes
308
-
309
- for audio_bytes, _ in audio_list:
310
- # Convertir les octets en un segment audio
311
- segment = AudioSegment.from_mp3(io.BytesIO(audio_bytes))
312
-
313
- # Ajouter le son de début, le segment TTS, le son de fin et le silence
314
- final_audio += begin_sound + segment + end_sound + silence
315
-
316
- # Convertir le segment audio final en octets
317
- buffer = io.BytesIO()
318
- final_audio.export(buffer, format="mp3")
319
- return buffer.getvalue()
320
- except IOError as e:
321
- print(f"Erreur lors de la lecture ou de l'écriture des fichiers audio : {e}")
322
- return None
323
- except Exception as e:
324
- print(f"Une erreur inattendue s'est produite : {e}")
325
- return None
326
-
327
- def process_message(
328
- message: str,
329
- operation_prompt: str = "",
330
- tts_enabled: bool = False
331
- ) -> str:
332
- """
333
- Traite les messages des utilisateurs et génère une réponse.
334
-
335
- Args:
336
- message (str): Le message d'entrée de l'utilisateur.
337
- operation_prompt (str, optional): Prompt supplémentaire pour l'opération. Par défaut "".
338
- tts_enabled (bool, optional): Si la synthèse vocale est activée. Par défaut False.
339
-
340
- """
341
- payload_content = f'{operation_prompt} :\n"""\n{message}\n"""'
342
- st.session_state.messages.append({"role": "user", "content": payload_content})
343
- full_response = ""
344
- try:
345
- for response in client.chat.completions.create(
346
- model="gpt-4o-mini",
347
- messages=st.session_state.messages,
348
- stream=True,
349
- temperature=0.1):
350
- full_response += (response.choices[0].delta.content or "")
351
- yield full_response + "▌"
352
-
353
- # Utiliser regex pour supprimer les trois premiers et derniers guillemets doubles
354
- full_response = re.sub(r'^"{3}|"{3}$', '', full_response.strip())
355
- st.session_state.messages.append({"role": "assistant", "content": full_response})
356
- st.session_state.full_response = full_response
357
- return full_response
358
- except Exception as e:
359
- st.error(f"Une erreur s'est produite lors de la génération de la réponse : {e}")
360
- return ""
361
-
362
- def process_tts_message(full_response: str) -> Tuple[Optional[bytes], Optional[float]]:
363
- try:
364
- tts_audio, tts_duration = text_to_speech(full_response)
365
- return tts_audio, tts_duration
366
- except Exception as e:
367
- st.error(f"Une erreur s'est produite lors de la conversion texte-parole : {e}")
368
- return None, None
369
-
370
- class GlobalSystemPrompts:
371
- """Class to store global system prompts."""
372
-
373
- @staticmethod
374
- def linguascribe():
375
- """
376
- Retrieve the system prompt for the Linguascribe feature.
377
-
378
- Returns:
379
- str: The system prompt for Linguascribe.
380
- """
381
- try:
382
- system_prompt = read_file('linguascribe.prompt')
383
- return system_prompt
384
- except FileNotFoundError:
385
- print("Le fichier 'linguascribe.prompt' n'a pas été trouvé.")
386
- return ""
387
- except IOError as e:
388
- print(f"Erreur lors de la lecture du fichier 'linguascribe.prompt': {e}")
389
- return ""
390
-
391
- # Function to configure the translation mode
392
- def set_translation_mode(from_lang: str, dest_lang: str) -> Tuple[str, str]:
393
- """
394
- Configure les prompts globaux pour le mode de traduction.
395
-
396
- Args:
397
- from_lang (str): La langue source.
398
- dest_lang (str): La langue de destination.
399
-
400
- Returns:
401
- Tuple[str, str]: Un tuple contenant le prompt système et le prompt d'opération.
402
- """
403
- system_prompt = GlobalSystemPrompts.linguascribe()
404
- operation_prompt = f"Translate({from_lang} to {dest_lang})"
405
- return system_prompt, operation_prompt
406
-
407
- # List of languages supported by the application
408
- SUPPORTED_LANGUAGES = [
409
- "Afrikaans", "Arabic", "Armenian", "Azerbaijani", "Belarusian", "Bosnian",
410
- "Bulgarian", "Catalan", "Chinese", "Croatian", "Czech", "Danish", "Dutch",
411
- "English", "Estonian", "Finnish", "French", "Galician", "German", "Greek",
412
- "Hebrew", "Hindi", "Hungarian", "Icelandic", "Indonesian", "Italian",
413
- "Japanese", "Kannada", "Kazakh", "Korean", "Latvian", "Lithuanian",
414
- "Macedonian", "Malay", "Marathi", "Maori", "Nepali", "Norwegian", "Persian",
415
- "Polish", "Portuguese", "Romanian", "Russian", "Serbian", "Slovak",
416
- "Slovenian", "Spanish", "Swahili", "Swedish", "Tagalog", "Tamil", "Thai",
417
- "Turkish", "Ukrainian", "Urdu", "Vietnamese", "Welsh"
418
- ]
419
-
420
- LANGUAGES_EMOJI = {
421
- "Afrikaans": "🇿🇦", "Arabic": "🇸🇦", "Armenian": "🇦🇲", "Azerbaijani": "🇦🇿", "Belarusian": "🇧🇾",
422
- "Bosnian": "🇧🇦", "Bulgarian": "🇧🇬", "Catalan": "🇪🇸", "Chinese": "🇨🇳", "Croatian": "🇭🇷",
423
- "Czech": "🇨🇿", "Danish": "🇩🇰", "Dutch": "🇳🇱", "English": "🇬🇧", "Estonian": "🇪🇪",
424
- "Finnish": "🇫🇮", "French": "🇫🇷", "Galician": "🇪🇸", "German": "🇩🇪", "Greek": "🇬🇷",
425
- "Hebrew": "🇮🇱", "Hindi": "🇮🇳", "Hungarian": "🇭🇺", "Icelandic": "🇮🇸", "Indonesian": "🇮🇩",
426
- "Italian": "🇮🇹", "Japanese": "🇯🇵", "Kannada": "🇮🇳", "Kazakh": "🇰🇿", "Korean": "🇰🇷",
427
- "Latvian": "🇱🇻", "Lithuanian": "🇱🇹", "Macedonian": "🇲🇰", "Malay": "🇲🇾", "Marathi": "🇮🇳",
428
- "Maori": "🇳🇿", "Nepali": "🇳🇵", "Norwegian": "🇳🇴", "Persian": "🇮🇷", "Polish": "🇵🇱",
429
- "Portuguese": "🇵🇹", "Romanian": "🇷🇴", "Russian": "🇷🇺", "Serbian": "🇷🇸", "Slovak": "🇸🇰",
430
- "Slovenian": "🇸🇮", "Spanish": "🇪🇸", "Swahili": "🇰🇪", "Swedish": "🇸🇪", "Tagalog": "🇵🇭",
431
- "Tamil": "🇮🇳", "Thai": "🇹🇭", "Turkish": "🇹🇷", "Ukrainian": "🇺🇦", "Urdu": "🇵🇰",
432
- "Vietnamese": "🇻🇳", "Welsh": "🏴󠁧󠁢󠁷󠁬󠁳󠁿"
433
- }
434
-
435
-
436
- def convert_iso6391_to_language_name(language_code: str) -> str:
437
- """
438
- Convertit un code ISO 639-1 en nom de langue.
439
-
440
- Args:
441
- language_code (str): Le code ISO 639-1 de la langue.
442
-
443
- Returns:
444
- str: Le nom de la langue correspondant au code ISO 639-1, ou 'English' si non trouvé.
445
- """
446
- # Dictionnaire associant les codes ISO 639-1 aux noms de langues
447
- iso_to_language: Dict[str, str] = {
448
- "af": "Afrikaans", "ar": "Arabic", "hy": "Armenian", "az": "Azerbaijani",
449
- "be": "Belarusian", "bs": "Bosnian", "bg": "Bulgarian", "ca": "Catalan",
450
- "zh": "Chinese", "hr": "Croatian", "cs": "Czech", "da": "Danish",
451
- "nl": "Dutch", "en": "English", "et": "Estonian", "fi": "Finnish",
452
- "fr": "French", "gl": "Galician", "de": "German", "el": "Greek",
453
- "he": "Hebrew", "hi": "Hindi", "hu": "Hungarian", "is": "Icelandic",
454
- "id": "Indonesian", "it": "Italian", "ja": "Japanese", "kn": "Kannada",
455
- "kk": "Kazakh", "ko": "Korean", "lv": "Latvian", "lt": "Lithuanian",
456
- "mk": "Macedonian", "ms": "Malay", "mr": "Marathi", "mi": "Maori",
457
- "ne": "Nepali", "no": "Norwegian", "fa": "Persian", "pl": "Polish",
458
- "pt": "Portuguese", "ro": "Romanian", "ru": "Russian", "sr": "Serbian",
459
- "sk": "Slovak", "sl": "Slovenian", "es": "Spanish", "sw": "Swahili",
460
- "sv": "Swedish", "tl": "Tagalog", "ta": "Tamil", "th": "Thai",
461
- "tr": "Turkish", "uk": "Ukrainian", "ur": "Urdu", "vi": "Vietnamese",
462
- "cy": "Welsh"
463
- }
464
-
465
- try:
466
- # Retourner le nom de la langue correspondant au code ISO 639-1
467
- return iso_to_language[language_code]
468
- except KeyError:
469
- # Gérer spécifiquement l'exception KeyError
470
- print(f"Code de langue non trouvé : {language_code}")
471
- return "English"
472
-
473
-
474
-
475
- def convert_language_name_to_iso6391(language_data: Union[str, Dict[str, str]]) -> str:
476
- """
477
- Convertit un nom de langue en son code ISO 639-1.
478
-
479
- Args:
480
- language_data (Union[str, Dict[str, str]]): Le nom de la langue ou un dictionnaire
481
- contenant le nom de la langue.
482
-
483
- Returns:
484
- str: Le code ISO 639-1 pour la langue donnée, ou 'en' si non trouvé.
485
- """
486
- # Dictionnaire associant les noms de langues aux codes ISO 639-1
487
- language_to_iso: Dict[str, str] = {
488
- "Afrikaans": "af", "Arabic": "ar", "Armenian": "hy", "Azerbaijani": "az",
489
- "Belarusian": "be", "Bosnian": "bs", "Bulgarian": "bg", "Catalan": "ca",
490
- "Chinese": "zh", "Croatian": "hr", "Czech": "cs", "Danish": "da",
491
- "Dutch": "nl", "English": "en", "Estonian": "et", "Finnish": "fi",
492
- "French": "fr", "Galician": "gl", "German": "de", "Greek": "el",
493
- "Hebrew": "he", "Hindi": "hi", "Hungarian": "hu", "Icelandic": "is",
494
- "Indonesian": "id", "Italian": "it", "Japanese": "ja", "Kannada": "kn",
495
- "Kazakh": "kk", "Korean": "ko", "Latvian": "lv", "Lithuanian": "lt",
496
- "Macedonian": "mk", "Malay": "ms", "Marathi": "mr", "Maori": "mi",
497
- "Nepali": "ne", "Norwegian": "no", "Persian": "fa", "Polish": "pl",
498
- "Portuguese": "pt", "Romanian": "ro", "Russian": "ru", "Serbian": "sr",
499
- "Slovak": "sk", "Slovenian": "sl", "Spanish": "es", "Swahili": "sw",
500
- "Swedish": "sv", "Tagalog": "tl", "Tamil": "ta", "Thai": "th",
501
- "Turkish": "tr", "Ukrainian": "uk", "Urdu": "ur", "Vietnamese": "vi",
502
- "Welsh": "cy"
503
- }
504
-
505
- # Vérifier si language_data est un dictionnaire
506
- if isinstance(language_data, dict):
507
- language_name = language_data.get('language', '')
508
  else:
509
- language_name = language_data
 
510
 
511
- try:
512
- # Retourner le code ISO 639-1 correspondant au nom de la langue
513
- return language_to_iso[language_name]
514
- except KeyError:
515
- # Gérer spécifiquement l'exception KeyError
516
- print(f"Langue non trouvée : {language_name}")
517
- return "en" # Par défaut 'en' si la langue n'est pas trouvée
518
 
519
- def on_languages_change() -> None:
520
- """Fonction de rappel pour le changement de langue(s) de destination."""
521
- selected_language_names: List[str] = st.session_state.language_selector
522
- st.session_state.selected_languages = [
523
- {"language": lang, "iso-639-1": convert_language_name_to_iso6391(lang)}
524
- for lang in selected_language_names
525
- ]
526
 
527
 
528
- def init_process_mode() -> Tuple[str, str]:
529
- """
530
- Initialise le mode de traitement pour la traduction si nécessaire.
 
 
 
 
 
 
 
 
 
531
 
532
- Returns:
533
- Tuple[str, str]: Un tuple contenant le prompt système et le prompt d'opération.
534
- """
535
- if st.session_state["process_mode"] == "translation":
536
- system_prompt, operation_prompt = set_translation_mode(
537
- from_lang=st.session_state.language_detected,
538
- dest_lang=st.session_state.target_language
539
- )
540
- return system_prompt, operation_prompt
541
- return "", ""
542
 
543
- # Fonction principale de l'application
544
  def main():
545
- """Fonction principale qui configure et exécute l'application Streamlit."""
546
-
547
- # Initialisation des variables d'état de session
548
- if "ui_loaded" not in st.session_state:
549
- st.session_state["ui_loaded"] = False
550
-
551
- if "language_detected" not in st.session_state:
552
- st.session_state["language_detected"] = None
553
-
554
- if "process_mode" not in st.session_state:
555
- st.session_state["process_mode"] = "translation"
556
-
557
- if "target_language" not in st.session_state:
558
- st.session_state.target_language = "en"
559
-
560
- if "selected_languages" not in st.session_state:
561
- st.session_state.selected_languages = [
562
- {"language": "English", "iso-639-1": "en"}
563
- ]
564
-
565
- if "interface_language" not in st.session_state:
566
- st.session_state.interface_language = "French" # Langue par défaut
567
-
568
- system_prompt, operation_prompt = init_process_mode()
569
-
570
- # Initialisation de l'historique des messages avec le prompt système
571
- if "messages" not in st.session_state:
572
- st.session_state.messages = []
573
-
574
- # Vérification de l'existence d'un message système dans st.session_state.messages
575
- if not any(message["role"] == "system" for message in st.session_state.messages):
576
- st.session_state.messages.insert(0, {"role": "system", "content": system_prompt})
577
-
578
- # Interface utilisateur pour le chat textuel
579
- if user_input := st.chat_input(get_translation("entrez_message")):
580
-
581
- with st.chat_message("user", avatar="👤"):
582
- st.markdown(user_input)
583
-
584
- # Traitement du message texte de l'utilisateur
585
- if st.session_state.language_detected is None:
586
- st.session_state.language_detected = detect_language(
587
- input_text=user_input, temperature=0.01
588
- )
589
-
590
- audio_list = []
591
-
592
- for cursor_selected_lang in st.session_state.selected_languages:
593
- st.session_state.target_language = cursor_selected_lang["iso-639-1"]
594
- st.session_state.full_response = ""
595
- # Initialisation du mode de traitement pour la langue cible actuelle
596
- system_prompt, operation_prompt = init_process_mode()
597
- target_language_name = cursor_selected_lang["language"]
598
- with st.status(f'({target_language_name}) - {get_translation("traduction_en_cours")}', expanded=True) as response_status:
599
- with st.chat_message("assistant", avatar="👻"):
600
- message_placeholder = st.empty()
601
- response_generator = process_message(
602
- user_input, operation_prompt, st.session_state.enable_tts_for_input_from_text_field
603
- )
604
- response_status.update(label=f'({target_language_name}) - {get_translation("traduction_en_cours")}', state="running", expanded=True)
605
- for response_chunk in response_generator:
606
- message_placeholder.markdown(response_chunk)
607
-
608
- full_response = response_generator.close() # Obtenir la réponse complète à la fin
609
- if st.session_state.full_response != "":
610
- message_placeholder.markdown(st.session_state.full_response)
611
-
612
- if st.session_state.enable_tts_for_input_from_text_field:
613
- response_status.update(label=f'({target_language_name}) - {get_translation("traduction_terminee")} ; {get_translation("synthese_vocale_en_cours")}', state="running", expanded=False)
614
- tts_audio, tts_duration = process_tts_message(st.session_state.full_response)
615
- if tts_audio:
616
- st.audio(tts_audio, format="audio/mp3", autoplay=False)
617
- audio_list.append((tts_audio, tts_duration))
618
- response_status.update(label=f'({target_language_name}) - {get_translation("traduction_terminee")} ; {get_translation("synthese_vocale_terminee")}', state="complete", expanded=False)
619
- else:
620
- response_status.update(label=f'({target_language_name}) - {get_translation("erreur_synthese_vocale")}', state="error", expanded=False)
621
-
622
-
623
- else:
624
- response_status.update(label=f'({target_language_name}) - {get_translation("traduction_terminee")}', state="complete", expanded=False)
625
- else:
626
- response_status.update(label=f'({target_language_name}) - {get_translation("erreur_traduction")}', state="error", expanded=False)
627
-
628
-
629
- if audio_list:
630
- with st.status(f"{get_translation('concatenation_audio_en_cours')}", expanded=False) as audio_status:
631
- audio_status.update(label=f"{get_translation('concatenation_audio_en_cours')}", state="running", expanded=False)
632
- try:
633
- final_audio = concatenate_audio_files(audio_list)
634
- with st.container(border=True):
635
-
636
- # Générer un nom de fichier unique
637
- timestamp = time.strftime("%Y%m%d-%H%M%S")
638
- langues = "_".join([lang["iso-639-1"] for lang in st.session_state.selected_languages])
639
- nom_fichier = f"reponse_audio_{langues}_{timestamp}.mp3"
640
-
641
 
642
- st.audio(final_audio, format="audio/mp3", autoplay=st.session_state.autoplay_tts)
643
-
644
- st.download_button(
645
- label=f"📥 {get_translation('telecharger_audio')}",
646
- data=final_audio,
647
- file_name=nom_fichier,
648
- mime="audio/mp3",
649
- use_container_width=True,
650
- type="primary",
651
- key=f"download_button_{langues}_{timestamp}",
652
- )
653
-
654
-
655
- audio_status.update(label=f"{get_translation('concatenation_audio_terminee')}", state="complete", expanded=True)
656
- except Exception as e:
657
- st.error(f"{get_translation('erreur_concatenation_audio')} : {str(e)}")
658
- audio_status.update(label=f"{get_translation('erreur_concatenation_audio')} : {str(e)}", state="error", expanded=True)
659
-
660
-
661
-
662
- with st.container(border=True):
663
- # Interface utilisateur pour l'enregistrement audio
664
- st.write(f"🗣️ {get_translation('enregistrez_message')}")
665
- if audio := audiorecorder(
666
- start_prompt=get_translation("cliquez_enregistrer"),
667
- stop_prompt=get_translation("cliquez_arreter"),
668
- pause_prompt=get_translation("cliquez_pause"),
669
- show_visualizer=True,
670
- key="vocal_chat_input"
671
- ):
672
- # Traitement de l'entrée audio de l'utilisateur
673
- if len(audio) > 0:
674
- #with st.status(get_translation("transcription_audio_en_cours"), expanded=False) as stt_status:
675
- #try:
676
- #stt_status.update(label=get_translation("transcription_audio_en_cours"), state="running", expanded=True)
677
- with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_audio:
678
- audio.export(temp_audio.name, format="wav")
679
- st.write(f"Frame rate: {audio.frame_rate}, Frame width: {audio.frame_width}, Duration: {audio.duration_seconds} seconds")
680
-
681
-
682
- # Transcrire l'audio en texte
683
- transcription = transcribe_audio(temp_audio, language=st.session_state.language_detected)
684
- # Detecter la langue du texte transcrit (si la langue source n'est pas détectée)
685
- if st.session_state.language_detected is None:
686
- st.session_state.language_detected = detect_language(
687
- input_text=transcription, temperature=0.01
688
- )
689
- st.markdown(
690
- f"- {get_translation('langue_detectee')}".format(
691
- f"{convert_iso6391_to_language_name(st.session_state.language_detected)}"
692
- )
693
- )
694
-
695
- st.markdown(
696
- f"🎤 {get_translation('transcription_audio')}".format(
697
- f"{transcription}"
698
- )
699
- )
700
-
701
-
702
- audio_list = []
703
- for cursor_selected_lang in st.session_state.selected_languages:
704
- st.session_state.target_language = cursor_selected_lang["iso-639-1"]
705
- st.session_state.full_response = ""
706
- # Initialisation du mode de traitement pour la langue cible actuelle
707
- system_prompt, operation_prompt = init_process_mode()
708
- with st.chat_message("assistant", avatar="👻"):
709
- message_placeholder = st.empty()
710
- response_generator = process_message(
711
- transcription, operation_prompt, st.session_state.enable_tts_for_input_from_audio_record
712
- )
713
- for response_chunk in response_generator:
714
- message_placeholder.markdown(response_chunk)
715
- full_response = response_generator.close()
716
- if st.session_state.full_response != "":
717
- message_placeholder.markdown(st.session_state.full_response)
718
-
719
- if st.session_state.enable_tts_for_input_from_audio_record:
720
- tts_audio, tts_duration = process_tts_message(st.session_state.full_response)
721
- if tts_audio:
722
- audio_list.append((tts_audio, tts_duration))
723
- else:
724
- pass
725
-
726
- if audio_list:
727
- #stt_status.update(label=f"{get_translation('concatenation_audio_en_cours')}", state="running", expanded=True)
728
- final_audio = concatenate_audio_files(audio_list)
729
- with st.container(border=True):
730
-
731
- # Générer un nom de fichier unique
732
- timestamp = time.strftime("%Y%m%d-%H%M%S")
733
- langues = "_".join([lang["iso-639-1"] for lang in st.session_state.selected_languages])
734
- nom_fichier = f"reponse_audio_{langues}_{timestamp}.mp3"
735
-
736
- st.audio(final_audio, format="audio/mp3", autoplay=st.session_state.autoplay_tts)
737
-
738
- st.download_button(
739
- label=f"📥 {get_translation('telecharger_audio')}",
740
- data=final_audio,
741
- file_name=nom_fichier,
742
- mime="audio/mp3",
743
- use_container_width=True,
744
- type="primary",
745
- key=f"download_button_{langues}_{timestamp}",
746
- )
747
-
748
- #stt_status.update(label=f"{get_translation('concatenation_audio_terminee')}", state="complete", expanded=True)
749
- #else:
750
- #stt_status.update(label=f"{get_translation('erreur_concatenation_audio')}", state="error", expanded=True)
751
- #except Exception as e:
752
- # st.error(f"{get_translation('erreur_transcription_audio')} : {str(e)}")
753
- # stt_status.update(label=f"{get_translation('erreur_transcription_audio')} : {str(e)}", state="error", expanded=True)
754
-
755
-
756
-
757
- # Configuration de la barre latérale
758
- with st.sidebar:
759
- st.logo("img/logo_2.png", icon_image="img/logo_2.png")
760
- st.header(get_translation("sidebar_titre"))
761
-
762
- with st.expander(f"{get_translation('a_propos')}",
763
- expanded=False,
764
- icon="ℹ️"):
765
- st.subheader(f"version: {__version__}")
766
- st.info(get_translation("info_app"))
767
-
768
- with st.container(border=True):
769
- st.subheader(get_translation("langue_interface"))
770
- # Sélection de la langue de l'interface
771
- st.selectbox(
772
- label=get_translation("choix_langue_interface"),
773
- options=list(translations.keys()),
774
- key="interface_language",
775
- index=(
776
- list(translations.keys()).index("French")
777
- if "interface_language" not in st.session_state
778
- else list(translations.keys()).index(st.session_state.interface_language)
779
- ),
780
- format_func=lambda lang: f"{LANGUAGES_EMOJI.get(lang, '')} {lang}"
781
- )
782
-
783
- with st.expander(f"{get_translation('selection_langue')}",
784
- expanded=True,
785
- icon="🌐"):
786
- # Conteneur pour la sélection de langue
787
-
788
- # Sélection multiple des langues de destination
789
- st.multiselect(
790
- label=get_translation("langues_destination"),
791
- placeholder=get_translation("placeholder_langues"),
792
- options=SUPPORTED_LANGUAGES,
793
- default=["English"],
794
- key="language_selector",
795
- max_selections=4,
796
- on_change=on_languages_change,
797
- format_func=lambda lang: f"{LANGUAGES_EMOJI.get(lang, '')} {lang}"
798
- )
799
-
800
- with st.expander(f"{get_translation('parametres_tts')}",
801
- expanded=True,
802
- icon="🔊"):
803
- st.selectbox(
804
- get_translation("choix_voix_tts"),
805
- options=["alloy", "echo", "fable", "onyx", "nova", "shimmer"],
806
- index=3, # "onyx" est à l'index 3
807
- key="tts_voice"
808
- )
809
- st.checkbox(
810
- get_translation("activer_tts_texte"),
811
- key="enable_tts_for_input_from_text_field",
812
- value=True
813
- )
814
- st.checkbox(
815
- get_translation("activer_tts_audio"),
816
- key="enable_tts_for_input_from_audio_record",
817
- value=True
818
- )
819
- st.checkbox(
820
- get_translation("lecture_auto_tts"),
821
- key="autoplay_tts",
822
- value=True
823
- )
824
 
825
  # Point d'entrée de l'application
826
  if __name__ == "__main__":
 
15
  from typing import Tuple
16
  from typing import Union
17
 
 
18
  # Third-party libraries
 
19
  import streamlit as st
20
+
 
 
21
 
22
 
23
  __version__ = "1.2.0"
24
 
25
  # Au début du fichier, après les imports
26
+ #st.set_page_config(
27
+ # page_title=f"DEMORRHA - (v{__version__})",
28
+ # page_icon="👹",
29
+ # layout="wide",
30
+ # initial_sidebar_state="collapsed"
31
+ #)
32
+
33
+
34
+ LANGUAGES_EMOJI = {
35
+ "Afrikaans": "🇿🇦", "Arabic": "🇸🇦", "Armenian": "🇦🇲", "Azerbaijani": "🇦🇿", "Belarusian": "🇧🇾",
36
+ "Bosnian": "🇧🇦", "Bulgarian": "🇧🇬", "Catalan": "🇪🇸", "Chinese": "🇨🇳", "Croatian": "🇭🇷",
37
+ "Czech": "🇨🇿", "Danish": "🇩🇰", "Dutch": "🇳🇱", "English": "🇬🇧", "Estonian": "🇪🇪",
38
+ "Finnish": "🇫🇮", "French": "🇫🇷", "Galician": "🇪🇸", "German": "🇩🇪", "Greek": "🇬🇷",
39
+ "Hebrew": "🇮🇱", "Hindi": "🇮🇳", "Hungarian": "🇭🇺", "Icelandic": "🇮🇸", "Indonesian": "🇮🇩",
40
+ "Italian": "🇮🇹", "Japanese": "🇯🇵", "Kannada": "🇮🇳", "Kazakh": "🇰🇿", "Korean": "🇰🇷",
41
+ "Latvian": "🇱🇻", "Lithuanian": "🇱🇹", "Macedonian": "🇲🇰", "Malay": "🇲🇾", "Marathi": "🇮🇳",
42
+ "Maori": "🇳🇿", "Nepali": "🇳🇵", "Norwegian": "🇳🇴", "Persian": "🇮🇷", "Polish": "🇵🇱",
43
+ "Portuguese": "🇵🇹", "Romanian": "🇷🇴", "Russian": "🇷🇺", "Serbian": "🇷🇸", "Slovak": "🇸🇰",
44
+ "Slovenian": "🇸🇮", "Spanish": "🇪🇸", "Swahili": "🇰🇪", "Swedish": "🇸🇪", "Tagalog": "🇵🇭",
45
+ "Tamil": "🇮🇳", "Thai": "🇹🇭", "Turkish": "🇹🇷", "Ukrainian": "🇺🇦", "Urdu": "🇵🇰",
46
+ "Vietnamese": "🇻🇳", "Welsh": "🏴󠁧󠁢󠁷󠁬󠁳󠁿"
47
+ }
48
+
49
 
50
  def load_ui_language(file_path: Optional[str] = "ui_lang_support.json") -> Dict[str, Any]:
51
  """
 
70
  print(f"{get_translation('erreur_lecture_fichier')} {e}")
71
  return {}
72
 
 
 
73
 
74
  def get_translation(key: str) -> str:
75
  """
 
81
  Returns:
82
  str: Le texte traduit.
83
  """
84
+ if "interface_language" not in st.session_state:
85
+ return translations[st.session_state.interface_language][key]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  else:
87
+ return translations[st.session_state.interface_language_select][key]
88
+
89
 
90
+ # Dictionary to store translations
91
+ translations = load_ui_language()
 
 
 
 
 
92
 
 
 
 
 
 
 
 
93
 
94
 
95
+ def language_selection_page():
96
+ """Page de sélection de la langue."""
97
+ st.title("Sélection de la langue")
98
+ selected_language = st.selectbox(
99
+ "Choisissez la langue de l'interface",
100
+ options=list(translations.keys()),
101
+ index=list(translations.keys()).index("English"),
102
+ format_func=lambda lang: f"{LANGUAGES_EMOJI.get(lang, '')} {lang}"
103
+ )
104
+ if st.button("Confirmer"):
105
+ st.session_state.interface_language_select = selected_language
106
+ st.switch_page("pages/main.py")
107
 
 
 
 
 
 
 
 
 
 
 
108
 
 
109
  def main():
110
+ """Fonction principale pour la navigation."""
111
+ if "interface_language_select" not in st.session_state:
112
+ st.session_state.interface_language_select = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
+ if st.session_state.interface_language_select is None:
115
+ language_selection_page()
116
+ else:
117
+ st.switch_page("pages/main.py")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
  # Point d'entrée de l'application
120
  if __name__ == "__main__":
pages/main.py ADDED
@@ -0,0 +1,818 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Standard libraries
2
+ import base64
3
+ import io
4
+ import json
5
+ import os
6
+ import re
7
+ import tempfile
8
+ import time
9
+ from os import getenv
10
+ from typing import Any
11
+ from typing import Dict
12
+ from typing import IO
13
+ from typing import List
14
+ from typing import Optional
15
+ from typing import Tuple
16
+ from typing import Union
17
+
18
+
19
+ # Third-party libraries
20
+ import requests
21
+ import streamlit as st
22
+ from audiorecorder import audiorecorder
23
+ from openai import OpenAI
24
+ from pydub import AudioSegment
25
+
26
+
27
+ __version__ = "1.2.0"
28
+
29
+ # Au début du fichier, après les imports
30
+ st.set_page_config(
31
+ page_title=f"DEMORRHA - (v{__version__})",
32
+ page_icon="👹",
33
+ layout="wide",
34
+ initial_sidebar_state="collapsed"
35
+ )
36
+
37
+ def load_ui_language(file_path: Optional[str] = "ui_lang_support.json") -> Dict[str, Any]:
38
+ """
39
+ Charge les traductions de l'interface utilisateur à partir d'un fichier JSON.
40
+
41
+ Args:
42
+ file_path (Optional[str]): Chemin vers le fichier JSON contenant les traductions.
43
+
44
+ Returns:
45
+ Dict[str, Any]: Un dictionnaire contenant les traductions de l'interface utilisateur.
46
+ """
47
+ try:
48
+ with open(file_path, 'r', encoding='utf-8') as file:
49
+ return json.load(file)
50
+ except FileNotFoundError:
51
+ print(f"{get_translation('erreur_fichier_non_trouve')} {file_path}")
52
+ return {}
53
+ except json.JSONDecodeError:
54
+ print(f"{get_translation('erreur_lecture_fichier')} JSON decoding error")
55
+ return {}
56
+ except IOError as e:
57
+ print(f"{get_translation('erreur_lecture_fichier')} {e}")
58
+ return {}
59
+
60
+ # Dictionary to store translations
61
+ translations = load_ui_language()
62
+
63
+ def get_translation(key: str) -> str:
64
+ """
65
+ Obtient la traduction pour une clé donnée basée sur la langue d'interface sélectionnée.
66
+
67
+ Args:
68
+ key (str): La clé de traduction.
69
+
70
+ Returns:
71
+ str: Le texte traduit.
72
+ """
73
+ if "interface_language" in st.session_state:
74
+ return translations[st.session_state.interface_language][key]
75
+ else:
76
+ return translations[st.session_state.interface_language_select][key]
77
+
78
+
79
+ # OpenAI client configuration with API key
80
+ client = OpenAI(api_key=getenv("OPENAI_API_KEY"))
81
+
82
+ def read_file(file_name: str) -> str:
83
+ """
84
+ Lit et retourne le contenu des fichiers texte.
85
+
86
+ Args:
87
+ file_name (str): Le nom du fichier à lire.
88
+
89
+ Returns:
90
+ str: Le contenu du fichier ou un message d'erreur.
91
+ """
92
+ try:
93
+ with open(file_name, 'r', encoding='utf-8') as file:
94
+ content = file.read()
95
+ return content
96
+ except FileNotFoundError:
97
+ return f"{get_translation('erreur_fichier_non_trouve')} {file_name}"
98
+ except IOError as e:
99
+ return f"{get_translation('erreur_lecture_fichier')} {str(e)}"
100
+
101
+ def split_audio(audio_file: str, max_size_mb: int = 25) -> List[str]:
102
+ """
103
+ Divise un fichier audio en segments de 25 Mo ou moins.
104
+
105
+ Args:
106
+ audio_file (str): Chemin vers le fichier audio.
107
+ max_size_mb (int): Taille maximale de chaque segment en Mo.
108
+
109
+ Returns:
110
+ List[str]: Liste des chemins vers les segments audio divisés.
111
+ """
112
+ try:
113
+ audio = AudioSegment.from_wav(audio_file)
114
+ duration_ms = len(audio)
115
+ segment_duration_ms = int(
116
+ (max_size_mb * 1024 * 1024 * 8) /
117
+ (audio.frame_rate * audio.sample_width * audio.channels)
118
+ )
119
+
120
+ segments = []
121
+ for start in range(0, duration_ms, segment_duration_ms):
122
+ end = min(start + segment_duration_ms, duration_ms)
123
+ segment = audio[start:end]
124
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_segment:
125
+ segment.export(temp_segment.name, format="wav")
126
+ segments.append(temp_segment.name)
127
+
128
+ return segments
129
+ except IOError as e:
130
+ print(f"Erreur lors de la lecture ou de l'écriture du fichier audio : {e}")
131
+ return []
132
+ except ValueError as e:
133
+ print(f"Erreur de valeur lors du traitement de l'audio : {e}")
134
+ return []
135
+
136
+ # Fonction modifiée pour transcrire l'audio en texte
137
+ def transcribe_audio(audio_file: IO, language: Optional[str] = None) -> str:
138
+ """
139
+ Transcrit un fichier audio en texte.
140
+
141
+ Args:
142
+ audio_file (IO): Le fichier audio à transcrire.
143
+ language (Optional[str]): La langue de l'audio. Par défaut None.
144
+
145
+ Returns:
146
+ str: Le texte transcrit.
147
+ """
148
+ max_size_mb = 25
149
+ file_size_mb = os.path.getsize(audio_file.name) / (1024 * 1024)
150
+
151
+ try:
152
+ with st.status("Transcription de l'audio en cours...") as status:
153
+ if file_size_mb > max_size_mb:
154
+ status.update(label="Découpage de l'audio en segments...")
155
+ segments = split_audio(audio_file.name, max_size_mb)
156
+ full_transcript = ""
157
+ for i, segment in enumerate(segments):
158
+ status.update(label=f"Transcription du segment {i+1}/{len(segments)}...")
159
+ with open(segment, "rb") as audio_segment:
160
+ transcript = client.audio.transcriptions.create(
161
+ model="whisper-1",
162
+ file=audio_segment,
163
+ language=language
164
+ )
165
+ full_transcript += f"{transcript.text} "
166
+ os.unlink(segment) # Supprime le fichier temporaire
167
+ status.update(label="Transcription terminée", state="complete")
168
+ return full_transcript.strip()
169
+ else:
170
+ status.update(label="Transcription de l'audio...")
171
+ with open(audio_file.name, "rb") as audio_file:
172
+ transcript = client.audio.transcriptions.create(
173
+ model="whisper-1",
174
+ file=audio_file,
175
+ language=language
176
+ )
177
+ status.update(label="Transcription terminée", state="complete")
178
+ return transcript.text
179
+ except IOError as e:
180
+ st.error(f"Erreur d'entrée/sortie lors de la transcription : {e}")
181
+ return ""
182
+ except Exception as e:
183
+ st.error(f"Erreur lors de la transcription : {e}")
184
+ return ""
185
+
186
+ # Fonction pour détecter la langue d'un texte donné
187
+ def detect_language(input_text: str, temperature: float = 0.01) -> str:
188
+ """
189
+ Détecte la langue d'un texte donné.
190
+
191
+ Args:
192
+ input_text (str): Le texte dont il faut détecter la langue.
193
+ temperature (float): La température pour le modèle de langage. Par défaut à 0.01.
194
+
195
+ Returns:
196
+ str: La langue détectée au format ISO-639-1.
197
+
198
+ Raises:
199
+ ValueError: Si la réponse de l'API est invalide.
200
+ requests.RequestException: En cas d'erreur de communication avec l'API.
201
+ """
202
+ system_prompt = (
203
+ "Agissez comme une fonction de détection de langue. "
204
+ "Je fournirai du texte dans n'importe quelle langue, et vous détecterez sa langue. "
205
+ "Fournissez le résultat de votre détection au format ISO-639-1. "
206
+ "Votre réponse doit représenter l'argument `language` et ne contenir "
207
+ "que sa valeur sous forme de chaîne. "
208
+ "Fournir la langue d'entrée au format ISO-639-1 améliorera la précision et la latence."
209
+ )
210
+ try:
211
+ response = client.chat.completions.create(
212
+ model="gpt-4o-mini",
213
+ temperature=temperature,
214
+ messages=[
215
+ {
216
+ "role": "system",
217
+ "content": system_prompt
218
+ },
219
+ {
220
+ "role": "user",
221
+ "content": input_text
222
+ }
223
+ ]
224
+ )
225
+ detected_language = response.choices[0].message.content
226
+ if not detected_language:
227
+ raise ValueError("La réponse de l'API est vide")
228
+ return detected_language
229
+ except requests.RequestException as e:
230
+ raise requests.RequestException(f"Erreur de communication avec l'API : {str(e)}")
231
+ except Exception as e:
232
+ raise ValueError(f"Erreur inattendue lors de la détection de la langue : {str(e)}")
233
+
234
+ def get_duration_pydub(audio_file: str) -> float:
235
+ """
236
+ Obtient la durée d'un fichier audio en utilisant pydub.
237
+
238
+ Args:
239
+ audio_file (str): Chemin vers le fichier audio.
240
+
241
+ Returns:
242
+ float: Durée du fichier audio en secondes.
243
+ """
244
+ try:
245
+ audio = AudioSegment.from_file(audio_file)
246
+ return audio.duration_seconds
247
+ except FileNotFoundError:
248
+ print(f"Erreur : Le fichier audio '{audio_file}' n'a pas été trouvé.")
249
+ return 0.0
250
+ except Exception as e:
251
+ print(f"Erreur lors de la lecture du fichier audio : {str(e)}")
252
+ return 0.0
253
+
254
+ def text_to_speech(text: str) -> Tuple[Optional[bytes], float]:
255
+ """
256
+ Convertit du texte en parole en utilisant l'API OpenAI.
257
+
258
+ Args:
259
+ text (str): Le texte à convertir en parole.
260
+
261
+ Returns:
262
+ Tuple[Optional[bytes], float]: Un tuple contenant les octets audio et la durée de l'audio en secondes.
263
+ """
264
+ try:
265
+ response = client.audio.speech.create(
266
+ model="tts-1",
267
+ voice=st.session_state.tts_voice,
268
+ input=text
269
+ )
270
+
271
+ # Sauvegarde l'audio dans un fichier temporaire
272
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio:
273
+ response.stream_to_file(temp_audio.name)
274
+
275
+ # Lit le contenu du fichier audio
276
+ with open(temp_audio.name, "rb") as audio_file:
277
+ audio_bytes = audio_file.read()
278
+
279
+ # Obtient la durée de l'audio en secondes
280
+ audio_duration = get_duration_pydub(temp_audio.name)
281
+
282
+ return audio_bytes, audio_duration
283
+ except Exception as e:
284
+ print(f"Erreur lors de la conversion texte-parole : {str(e)}")
285
+ return None, 0.0
286
+
287
+ def concatenate_audio_files(audio_list: List[Tuple[bytes, float]]) -> Optional[bytes]:
288
+ """
289
+ Concatène plusieurs fichiers audio avec des effets sonores.
290
+
291
+ Args:
292
+ audio_list (List[Tuple[bytes, float]]): Une liste de tuples, chacun contenant
293
+ des octets audio et la durée.
294
+
295
+ Returns:
296
+ Optional[bytes]: L'audio concaténé sous forme d'octets, ou None en cas d'erreur.
297
+ """
298
+ # Créer un segment audio vide
299
+ final_audio = AudioSegment.empty()
300
+
301
+ try:
302
+ # Charger les effets sonores
303
+ begin_sound = AudioSegment.from_mp3(
304
+ "sound-effects/voice-message-play-begin/voice-message-play-begin-1.mp3"
305
+ )
306
+ end_sound = AudioSegment.from_mp3(
307
+ "sound-effects/voice-message-play-ending/voice-message-play-ending-1.mp3"
308
+ )
309
+
310
+ # 5 secondes de silence
311
+ silence = AudioSegment.silent(duration=1500) # 1500 ms = 1.5 secondes
312
+
313
+ for audio_bytes, _ in audio_list:
314
+ # Convertir les octets en un segment audio
315
+ segment = AudioSegment.from_mp3(io.BytesIO(audio_bytes))
316
+
317
+ # Ajouter le son de début, le segment TTS, le son de fin et le silence
318
+ final_audio += begin_sound + segment + end_sound + silence
319
+
320
+ # Convertir le segment audio final en octets
321
+ buffer = io.BytesIO()
322
+ final_audio.export(buffer, format="mp3")
323
+ return buffer.getvalue()
324
+ except IOError as e:
325
+ print(f"Erreur lors de la lecture ou de l'écriture des fichiers audio : {e}")
326
+ return None
327
+ except Exception as e:
328
+ print(f"Une erreur inattendue s'est produite : {e}")
329
+ return None
330
+
331
+ def process_message(
332
+ message: str,
333
+ operation_prompt: str = "",
334
+ tts_enabled: bool = False
335
+ ) -> str:
336
+ """
337
+ Traite les messages des utilisateurs et génère une réponse.
338
+
339
+ Args:
340
+ message (str): Le message d'entrée de l'utilisateur.
341
+ operation_prompt (str, optional): Prompt supplémentaire pour l'opération. Par défaut "".
342
+ tts_enabled (bool, optional): Si la synthèse vocale est activée. Par défaut False.
343
+
344
+ """
345
+ payload_content = f'{operation_prompt} :\n"""\n{message}\n"""'
346
+ st.session_state.messages.append({"role": "user", "content": payload_content})
347
+ full_response = ""
348
+ try:
349
+ for response in client.chat.completions.create(
350
+ model="gpt-4o-mini",
351
+ messages=st.session_state.messages,
352
+ stream=True,
353
+ temperature=0.1):
354
+ full_response += (response.choices[0].delta.content or "")
355
+ yield full_response + "▌"
356
+
357
+ # Utiliser regex pour supprimer les trois premiers et derniers guillemets doubles
358
+ full_response = re.sub(r'^"{3}|"{3}$', '', full_response.strip())
359
+ st.session_state.messages.append({"role": "assistant", "content": full_response})
360
+ st.session_state.full_response = full_response
361
+ return full_response
362
+ except Exception as e:
363
+ st.error(f"Une erreur s'est produite lors de la génération de la réponse : {e}")
364
+ return ""
365
+
366
+ def process_tts_message(full_response: str) -> Tuple[Optional[bytes], Optional[float]]:
367
+ try:
368
+ tts_audio, tts_duration = text_to_speech(full_response)
369
+ return tts_audio, tts_duration
370
+ except Exception as e:
371
+ st.error(f"Une erreur s'est produite lors de la conversion texte-parole : {e}")
372
+ return None, None
373
+
374
+ class GlobalSystemPrompts:
375
+ """Class to store global system prompts."""
376
+
377
+ @staticmethod
378
+ def linguascribe():
379
+ """
380
+ Retrieve the system prompt for the Linguascribe feature.
381
+
382
+ Returns:
383
+ str: The system prompt for Linguascribe.
384
+ """
385
+ try:
386
+ system_prompt = read_file('linguascribe.prompt')
387
+ return system_prompt
388
+ except FileNotFoundError:
389
+ print("Le fichier 'linguascribe.prompt' n'a pas été trouvé.")
390
+ return ""
391
+ except IOError as e:
392
+ print(f"Erreur lors de la lecture du fichier 'linguascribe.prompt': {e}")
393
+ return ""
394
+
395
+ # Function to configure the translation mode
396
+ def set_translation_mode(from_lang: str, dest_lang: str) -> Tuple[str, str]:
397
+ """
398
+ Configure les prompts globaux pour le mode de traduction.
399
+
400
+ Args:
401
+ from_lang (str): La langue source.
402
+ dest_lang (str): La langue de destination.
403
+
404
+ Returns:
405
+ Tuple[str, str]: Un tuple contenant le prompt système et le prompt d'opération.
406
+ """
407
+ system_prompt = GlobalSystemPrompts.linguascribe()
408
+ operation_prompt = f"Translate({from_lang} to {dest_lang})"
409
+ return system_prompt, operation_prompt
410
+
411
+ # List of languages supported by the application
412
+ SUPPORTED_LANGUAGES = [
413
+ "Afrikaans", "Arabic", "Armenian", "Azerbaijani", "Belarusian", "Bosnian",
414
+ "Bulgarian", "Catalan", "Chinese", "Croatian", "Czech", "Danish", "Dutch",
415
+ "English", "Estonian", "Finnish", "French", "Galician", "German", "Greek",
416
+ "Hebrew", "Hindi", "Hungarian", "Icelandic", "Indonesian", "Italian",
417
+ "Japanese", "Kannada", "Kazakh", "Korean", "Latvian", "Lithuanian",
418
+ "Macedonian", "Malay", "Marathi", "Maori", "Nepali", "Norwegian", "Persian",
419
+ "Polish", "Portuguese", "Romanian", "Russian", "Serbian", "Slovak",
420
+ "Slovenian", "Spanish", "Swahili", "Swedish", "Tagalog", "Tamil", "Thai",
421
+ "Turkish", "Ukrainian", "Urdu", "Vietnamese", "Welsh"
422
+ ]
423
+
424
+ LANGUAGES_EMOJI = {
425
+ "Afrikaans": "🇿🇦", "Arabic": "🇸🇦", "Armenian": "🇦🇲", "Azerbaijani": "🇦🇿", "Belarusian": "🇧🇾",
426
+ "Bosnian": "🇧🇦", "Bulgarian": "🇧🇬", "Catalan": "🇪🇸", "Chinese": "🇨🇳", "Croatian": "🇭🇷",
427
+ "Czech": "🇨🇿", "Danish": "🇩🇰", "Dutch": "🇳🇱", "English": "🇬🇧", "Estonian": "🇪🇪",
428
+ "Finnish": "🇫🇮", "French": "🇫🇷", "Galician": "🇪🇸", "German": "🇩🇪", "Greek": "🇬🇷",
429
+ "Hebrew": "🇮🇱", "Hindi": "🇮🇳", "Hungarian": "🇭🇺", "Icelandic": "🇮🇸", "Indonesian": "🇮🇩",
430
+ "Italian": "🇮🇹", "Japanese": "🇯🇵", "Kannada": "🇮🇳", "Kazakh": "🇰🇿", "Korean": "🇰🇷",
431
+ "Latvian": "🇱🇻", "Lithuanian": "🇱🇹", "Macedonian": "🇲🇰", "Malay": "🇲🇾", "Marathi": "🇮🇳",
432
+ "Maori": "🇳🇿", "Nepali": "🇳🇵", "Norwegian": "🇳🇴", "Persian": "🇮🇷", "Polish": "🇵🇱",
433
+ "Portuguese": "🇵🇹", "Romanian": "🇷🇴", "Russian": "🇷🇺", "Serbian": "🇷🇸", "Slovak": "🇸🇰",
434
+ "Slovenian": "🇸🇮", "Spanish": "🇪🇸", "Swahili": "🇰🇪", "Swedish": "🇸🇪", "Tagalog": "🇵🇭",
435
+ "Tamil": "🇮🇳", "Thai": "🇹🇭", "Turkish": "🇹🇷", "Ukrainian": "🇺🇦", "Urdu": "🇵🇰",
436
+ "Vietnamese": "🇻🇳", "Welsh": "🏴󠁧󠁢󠁷󠁬󠁳󠁿"
437
+ }
438
+
439
+
440
+ def convert_iso6391_to_language_name(language_code: str) -> str:
441
+ """
442
+ Convertit un code ISO 639-1 en nom de langue.
443
+
444
+ Args:
445
+ language_code (str): Le code ISO 639-1 de la langue.
446
+
447
+ Returns:
448
+ str: Le nom de la langue correspondant au code ISO 639-1, ou 'English' si non trouvé.
449
+ """
450
+ # Dictionnaire associant les codes ISO 639-1 aux noms de langues
451
+ iso_to_language: Dict[str, str] = {
452
+ "af": "Afrikaans", "ar": "Arabic", "hy": "Armenian", "az": "Azerbaijani",
453
+ "be": "Belarusian", "bs": "Bosnian", "bg": "Bulgarian", "ca": "Catalan",
454
+ "zh": "Chinese", "hr": "Croatian", "cs": "Czech", "da": "Danish",
455
+ "nl": "Dutch", "en": "English", "et": "Estonian", "fi": "Finnish",
456
+ "fr": "French", "gl": "Galician", "de": "German", "el": "Greek",
457
+ "he": "Hebrew", "hi": "Hindi", "hu": "Hungarian", "is": "Icelandic",
458
+ "id": "Indonesian", "it": "Italian", "ja": "Japanese", "kn": "Kannada",
459
+ "kk": "Kazakh", "ko": "Korean", "lv": "Latvian", "lt": "Lithuanian",
460
+ "mk": "Macedonian", "ms": "Malay", "mr": "Marathi", "mi": "Maori",
461
+ "ne": "Nepali", "no": "Norwegian", "fa": "Persian", "pl": "Polish",
462
+ "pt": "Portuguese", "ro": "Romanian", "ru": "Russian", "sr": "Serbian",
463
+ "sk": "Slovak", "sl": "Slovenian", "es": "Spanish", "sw": "Swahili",
464
+ "sv": "Swedish", "tl": "Tagalog", "ta": "Tamil", "th": "Thai",
465
+ "tr": "Turkish", "uk": "Ukrainian", "ur": "Urdu", "vi": "Vietnamese",
466
+ "cy": "Welsh"
467
+ }
468
+
469
+ try:
470
+ # Retourner le nom de la langue correspondant au code ISO 639-1
471
+ return iso_to_language[language_code]
472
+ except KeyError:
473
+ # Gérer spécifiquement l'exception KeyError
474
+ print(f"Code de langue non trouvé : {language_code}")
475
+ return "English"
476
+
477
+
478
+
479
+ def convert_language_name_to_iso6391(language_data: Union[str, Dict[str, str]]) -> str:
480
+ """
481
+ Convertit un nom de langue en son code ISO 639-1.
482
+
483
+ Args:
484
+ language_data (Union[str, Dict[str, str]]): Le nom de la langue ou un dictionnaire
485
+ contenant le nom de la langue.
486
+
487
+ Returns:
488
+ str: Le code ISO 639-1 pour la langue donnée, ou 'en' si non trouvé.
489
+ """
490
+ # Dictionnaire associant les noms de langues aux codes ISO 639-1
491
+ language_to_iso: Dict[str, str] = {
492
+ "Afrikaans": "af", "Arabic": "ar", "Armenian": "hy", "Azerbaijani": "az",
493
+ "Belarusian": "be", "Bosnian": "bs", "Bulgarian": "bg", "Catalan": "ca",
494
+ "Chinese": "zh", "Croatian": "hr", "Czech": "cs", "Danish": "da",
495
+ "Dutch": "nl", "English": "en", "Estonian": "et", "Finnish": "fi",
496
+ "French": "fr", "Galician": "gl", "German": "de", "Greek": "el",
497
+ "Hebrew": "he", "Hindi": "hi", "Hungarian": "hu", "Icelandic": "is",
498
+ "Indonesian": "id", "Italian": "it", "Japanese": "ja", "Kannada": "kn",
499
+ "Kazakh": "kk", "Korean": "ko", "Latvian": "lv", "Lithuanian": "lt",
500
+ "Macedonian": "mk", "Malay": "ms", "Marathi": "mr", "Maori": "mi",
501
+ "Nepali": "ne", "Norwegian": "no", "Persian": "fa", "Polish": "pl",
502
+ "Portuguese": "pt", "Romanian": "ro", "Russian": "ru", "Serbian": "sr",
503
+ "Slovak": "sk", "Slovenian": "sl", "Spanish": "es", "Swahili": "sw",
504
+ "Swedish": "sv", "Tagalog": "tl", "Tamil": "ta", "Thai": "th",
505
+ "Turkish": "tr", "Ukrainian": "uk", "Urdu": "ur", "Vietnamese": "vi",
506
+ "Welsh": "cy"
507
+ }
508
+
509
+ # Vérifier si language_data est un dictionnaire
510
+ if isinstance(language_data, dict):
511
+ language_name = language_data.get('language', '')
512
+ else:
513
+ language_name = language_data
514
+
515
+ try:
516
+ # Retourner le code ISO 639-1 correspondant au nom de la langue
517
+ return language_to_iso[language_name]
518
+ except KeyError:
519
+ # Gérer spécifiquement l'exception KeyError
520
+ print(f"Langue non trouvée : {language_name}")
521
+ return "en" # Par défaut 'en' si la langue n'est pas trouvée
522
+
523
+ def on_languages_change() -> None:
524
+ """Fonction de rappel pour le changement de langue(s) de destination."""
525
+ selected_language_names: List[str] = st.session_state.language_selector
526
+ st.session_state.selected_languages = [
527
+ {"language": lang, "iso-639-1": convert_language_name_to_iso6391(lang)}
528
+ for lang in selected_language_names
529
+ ]
530
+
531
+
532
+ def init_process_mode() -> Tuple[str, str]:
533
+ """
534
+ Initialise le mode de traitement pour la traduction si nécessaire.
535
+
536
+ Returns:
537
+ Tuple[str, str]: Un tuple contenant le prompt système et le prompt d'opération.
538
+ """
539
+ if st.session_state["process_mode"] == "translation":
540
+ system_prompt, operation_prompt = set_translation_mode(
541
+ from_lang=st.session_state.language_detected,
542
+ dest_lang=st.session_state.target_language
543
+ )
544
+ return system_prompt, operation_prompt
545
+ return "", ""
546
+
547
+
548
+ def main_page():
549
+ """Page principale de l'application."""
550
+
551
+ # Initialisation des variables d'état de session
552
+ if "ui_loaded" not in st.session_state:
553
+ st.session_state["ui_loaded"] = False
554
+
555
+ if "language_detected" not in st.session_state:
556
+ st.session_state["language_detected"] = None
557
+
558
+ if "process_mode" not in st.session_state:
559
+ st.session_state["process_mode"] = "translation"
560
+
561
+ if "target_language" not in st.session_state:
562
+ st.session_state.target_language = "en"
563
+
564
+ if "selected_languages" not in st.session_state:
565
+ st.session_state.selected_languages = [
566
+ {"language": "English", "iso-639-1": "en"}
567
+ ]
568
+
569
+ if "interface_language_select" not in st.session_state:
570
+ st.session_state.interface_language_select = "English" # Langue par défaut
571
+
572
+ system_prompt, operation_prompt = init_process_mode()
573
+
574
+ # Initialisation de l'historique des messages avec le prompt système
575
+ if "messages" not in st.session_state:
576
+ st.session_state.messages = []
577
+
578
+ # Vérification de l'existence d'un message système dans st.session_state.messages
579
+ if not any(message["role"] == "system" for message in st.session_state.messages):
580
+ st.session_state.messages.insert(0, {"role": "system", "content": system_prompt})
581
+
582
+ # Interface utilisateur pour le chat textuel
583
+ if user_input := st.chat_input(get_translation("entrez_message")):
584
+
585
+ with st.chat_message("user", avatar="👤"):
586
+ st.markdown(user_input)
587
+
588
+ # Traitement du message texte de l'utilisateur
589
+ if st.session_state.language_detected is None:
590
+ st.session_state.language_detected = detect_language(
591
+ input_text=user_input, temperature=0.01
592
+ )
593
+
594
+ audio_list = []
595
+
596
+ for cursor_selected_lang in st.session_state.selected_languages:
597
+ st.session_state.target_language = cursor_selected_lang["iso-639-1"]
598
+ st.session_state.full_response = ""
599
+ # Initialisation du mode de traitement pour la langue cible actuelle
600
+ system_prompt, operation_prompt = init_process_mode()
601
+ target_language_name = cursor_selected_lang["language"]
602
+ with st.status(f'({target_language_name}) - {get_translation("traduction_en_cours")}', expanded=True) as response_status:
603
+ with st.chat_message("assistant", avatar="👻"):
604
+ message_placeholder = st.empty()
605
+ response_generator = process_message(
606
+ user_input, operation_prompt, st.session_state.enable_tts_for_input_from_text_field
607
+ )
608
+ response_status.update(label=f'({target_language_name}) - {get_translation("traduction_en_cours")}', state="running", expanded=True)
609
+ for response_chunk in response_generator:
610
+ message_placeholder.markdown(response_chunk)
611
+
612
+ full_response = response_generator.close() # Obtenir la réponse complète à la fin
613
+ if st.session_state.full_response != "":
614
+ message_placeholder.markdown(st.session_state.full_response)
615
+
616
+ if st.session_state.enable_tts_for_input_from_text_field:
617
+ response_status.update(label=f'({target_language_name}) - {get_translation("traduction_terminee")} ; {get_translation("synthese_vocale_en_cours")}', state="running", expanded=False)
618
+ tts_audio, tts_duration = process_tts_message(st.session_state.full_response)
619
+ if tts_audio:
620
+ st.audio(tts_audio, format="audio/mp3", autoplay=False)
621
+ audio_list.append((tts_audio, tts_duration))
622
+ response_status.update(label=f'({target_language_name}) - {get_translation("traduction_terminee")} ; {get_translation("synthese_vocale_terminee")}', state="complete", expanded=False)
623
+ else:
624
+ response_status.update(label=f'({target_language_name}) - {get_translation("erreur_synthese_vocale")}', state="error", expanded=False)
625
+
626
+
627
+ else:
628
+ response_status.update(label=f'({target_language_name}) - {get_translation("traduction_terminee")}', state="complete", expanded=False)
629
+ else:
630
+ response_status.update(label=f'({target_language_name}) - {get_translation("erreur_traduction")}', state="error", expanded=False)
631
+
632
+
633
+ if audio_list:
634
+ with st.status(f"{get_translation('concatenation_audio_en_cours')}", expanded=False) as audio_status:
635
+ audio_status.update(label=f"{get_translation('concatenation_audio_en_cours')}", state="running", expanded=False)
636
+ try:
637
+ final_audio = concatenate_audio_files(audio_list)
638
+ with st.container(border=True):
639
+
640
+ # Générer un nom de fichier unique
641
+ timestamp = time.strftime("%Y%m%d-%H%M%S")
642
+ langues = "_".join([lang["iso-639-1"] for lang in st.session_state.selected_languages])
643
+ nom_fichier = f"reponse_audio_{langues}_{timestamp}.mp3"
644
+
645
+
646
+ st.audio(final_audio, format="audio/mp3", autoplay=st.session_state.autoplay_tts)
647
+
648
+ st.download_button(
649
+ label=f"📥 {get_translation('telecharger_audio')}",
650
+ data=final_audio,
651
+ file_name=nom_fichier,
652
+ mime="audio/mp3",
653
+ use_container_width=True,
654
+ type="primary",
655
+ key=f"download_button_{langues}_{timestamp}",
656
+ )
657
+
658
+
659
+ audio_status.update(label=f"{get_translation('concatenation_audio_terminee')}", state="complete", expanded=True)
660
+ except Exception as e:
661
+ st.error(f"{get_translation('erreur_concatenation_audio')} : {str(e)}")
662
+ audio_status.update(label=f"{get_translation('erreur_concatenation_audio')} : {str(e)}", state="error", expanded=True)
663
+
664
+
665
+
666
+ with st.container(border=True):
667
+ # Interface utilisateur pour l'enregistrement audio
668
+ st.write(f"🗣️ {get_translation('enregistrez_message')}")
669
+ if audio := audiorecorder(
670
+ start_prompt=get_translation("cliquez_enregistrer"),
671
+ stop_prompt=get_translation("cliquez_arreter"),
672
+ pause_prompt=get_translation("cliquez_pause"),
673
+ show_visualizer=True,
674
+ key="vocal_chat_input"
675
+ ):
676
+ # Traitement de l'entrée audio de l'utilisateur
677
+ if len(audio) > 0:
678
+ #with st.status(get_translation("transcription_audio_en_cours"), expanded=False) as stt_status:
679
+ #try:
680
+ #stt_status.update(label=get_translation("transcription_audio_en_cours"), state="running", expanded=True)
681
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_audio:
682
+ audio.export(temp_audio.name, format="wav")
683
+ st.write(f"Frame rate: {audio.frame_rate}, Frame width: {audio.frame_width}, Duration: {audio.duration_seconds} seconds")
684
+
685
+
686
+ # Transcrire l'audio en texte
687
+ transcription = transcribe_audio(temp_audio, language=st.session_state.language_detected)
688
+ # Detecter la langue du texte transcrit (si la langue source n'est pas détectée)
689
+ if st.session_state.language_detected is None:
690
+ st.session_state.language_detected = detect_language(
691
+ input_text=transcription, temperature=0.01
692
+ )
693
+ st.markdown(
694
+ f"- {get_translation('langue_detectee')}".format(
695
+ f"{convert_iso6391_to_language_name(st.session_state.language_detected)}"
696
+ )
697
+ )
698
+
699
+ st.markdown(
700
+ f"🎤 {get_translation('transcription_audio')}".format(
701
+ f"{transcription}"
702
+ )
703
+ )
704
+
705
+
706
+ audio_list = []
707
+ for cursor_selected_lang in st.session_state.selected_languages:
708
+ st.session_state.target_language = cursor_selected_lang["iso-639-1"]
709
+ st.session_state.full_response = ""
710
+ # Initialisation du mode de traitement pour la langue cible actuelle
711
+ system_prompt, operation_prompt = init_process_mode()
712
+ with st.chat_message("assistant", avatar="👻"):
713
+ message_placeholder = st.empty()
714
+ response_generator = process_message(
715
+ transcription, operation_prompt, st.session_state.enable_tts_for_input_from_audio_record
716
+ )
717
+ for response_chunk in response_generator:
718
+ message_placeholder.markdown(response_chunk)
719
+ full_response = response_generator.close()
720
+ if st.session_state.full_response != "":
721
+ message_placeholder.markdown(st.session_state.full_response)
722
+
723
+ if st.session_state.enable_tts_for_input_from_audio_record:
724
+ tts_audio, tts_duration = process_tts_message(st.session_state.full_response)
725
+ if tts_audio:
726
+ audio_list.append((tts_audio, tts_duration))
727
+ else:
728
+ pass
729
+
730
+ if audio_list:
731
+ #stt_status.update(label=f"{get_translation('concatenation_audio_en_cours')}", state="running", expanded=True)
732
+ final_audio = concatenate_audio_files(audio_list)
733
+ with st.container(border=True):
734
+
735
+ # Générer un nom de fichier unique
736
+ timestamp = time.strftime("%Y%m%d-%H%M%S")
737
+ langues = "_".join([lang["iso-639-1"] for lang in st.session_state.selected_languages])
738
+ nom_fichier = f"reponse_audio_{langues}_{timestamp}.mp3"
739
+
740
+ st.audio(final_audio, format="audio/mp3", autoplay=st.session_state.autoplay_tts)
741
+
742
+ st.download_button(
743
+ label=f"📥 {get_translation('telecharger_audio')}",
744
+ data=final_audio,
745
+ file_name=nom_fichier,
746
+ mime="audio/mp3",
747
+ use_container_width=True,
748
+ type="primary",
749
+ key=f"download_button_{langues}_{timestamp}",
750
+ )
751
+
752
+ #stt_status.update(label=f"{get_translation('concatenation_audio_terminee')}", state="complete", expanded=True)
753
+ #else:
754
+ #stt_status.update(label=f"{get_translation('erreur_concatenation_audio')}", state="error", expanded=True)
755
+ #except Exception as e:
756
+ # st.error(f"{get_translation('erreur_transcription_audio')} : {str(e)}")
757
+ # stt_status.update(label=f"{get_translation('erreur_transcription_audio')} : {str(e)}", state="error", expanded=True)
758
+
759
+
760
+
761
+ # Configuration de la barre latérale
762
+ with st.sidebar:
763
+ st.logo("img/logo_2.png", icon_image="img/logo_2.png")
764
+ st.header(get_translation("sidebar_titre"))
765
+
766
+ with st.expander(f"{get_translation('a_propos')}",
767
+ expanded=False,
768
+ icon="ℹ️"):
769
+ st.subheader(f"version: {__version__}")
770
+ st.info(get_translation("info_app"))
771
+
772
+
773
+
774
+ with st.expander(f"{get_translation('selection_langue')}",
775
+ expanded=True,
776
+ icon="🌐"):
777
+ # Conteneur pour la sélection de langue
778
+
779
+ # Sélection multiple des langues de destination
780
+ st.multiselect(
781
+ label=get_translation("langues_destination"),
782
+ placeholder=get_translation("placeholder_langues"),
783
+ options=SUPPORTED_LANGUAGES,
784
+ default=["English"],
785
+ key="language_selector",
786
+ max_selections=4,
787
+ on_change=on_languages_change,
788
+ format_func=lambda lang: f"{LANGUAGES_EMOJI.get(lang, '')} {lang}"
789
+ )
790
+
791
+ with st.expander(f"{get_translation('parametres_tts')}",
792
+ expanded=True,
793
+ icon="🔊"):
794
+ st.selectbox(
795
+ get_translation("choix_voix_tts"),
796
+ options=["alloy", "echo", "fable", "onyx", "nova", "shimmer"],
797
+ index=3, # "onyx" est à l'index 3
798
+ key="tts_voice"
799
+ )
800
+ st.checkbox(
801
+ get_translation("activer_tts_texte"),
802
+ key="enable_tts_for_input_from_text_field",
803
+ value=True
804
+ )
805
+ st.checkbox(
806
+ get_translation("activer_tts_audio"),
807
+ key="enable_tts_for_input_from_audio_record",
808
+ value=True
809
+ )
810
+ st.checkbox(
811
+ get_translation("lecture_auto_tts"),
812
+ key="autoplay_tts",
813
+ value=True
814
+ )
815
+
816
+
817
+
818
+ main_page()