File size: 18,280 Bytes
4688078
 
 
3f4fb6f
0f33e60
be0e7b0
ee77673
 
63a6090
4688078
6ff8c22
6971407
 
6ff8c22
bb915d7
 
 
 
 
 
 
 
 
 
ee77673
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6971407
ee77673
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
0f33e60
 
6971407
 
0f33e60
ee77673
0f33e60
6ff8c22
6971407
 
 
 
6ff8c22
 
6971407
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63a6090
 
 
 
6ff8c22
be0e7b0
 
 
865139d
be0e7b0
 
 
 
 
 
 
 
 
 
63a6090
 
 
 
 
 
 
 
 
be0e7b0
9509de8
 
 
 
63a6090
 
 
 
 
 
 
9509de8
 
63a6090
 
 
 
 
be0e7b0
6ff8c22
63a6090
 
 
f5b1c77
 
 
0f33e60
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23d2281
29e90a0
63a6090
 
 
 
29e90a0
606dc5d
6ff8c22
ba0e382
6ff8c22
ba0e382
 
 
73b0775
6ff8c22
73b0775
 
 
6ff8c22
29e90a0
73b0775
 
29e90a0
 
6971407
6ff8c22
ba0e382
6971407
6ff8c22
52aa07f
73b0775
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52aa07f
 
 
 
 
 
73b0775
 
 
6ff8c22
4688078
6971407
4688078
6971407
 
 
ba0e382
 
 
 
29e90a0
 
 
 
 
c151267
606dc5d
 
 
 
 
 
c151267
 
 
29e90a0
 
c151267
 
 
23d2281
4688078
36c1b32
4688078
36c1b32
 
 
4688078
6ff8c22
36c1b32
 
 
 
3f0583d
63a6090
29e90a0
3f0583d
29e90a0
 
36c1b32
29e90a0
 
b2ae304
 
 
 
 
63a6090
 
 
 
 
 
 
 
29e90a0
 
36c1b32
 
 
 
 
c151267
 
 
3694efb
 
 
 
 
 
0f33e60
29e90a0
6ff8c22
3f4fb6f
ee77673
 
 
 
6971407
 
6ff8c22
29e90a0
0f33e60
29e90a0
b2ae304
29e90a0
3f0583d
29e90a0
 
 
 
 
b2ae304
 
 
 
 
 
 
 
 
 
 
 
4688078
6ff8c22
6971407
 
 
 
6ff8c22
 
 
6971407
4688078
92465f3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29e90a0
 
92465f3
3f0583d
 
 
 
 
 
606dc5d
29e90a0
 
e845f52
29e90a0
e845f52
29e90a0
e845f52
29e90a0
e845f52
 
606dc5d
29e90a0
 
606dc5d
 
865139d
 
 
 
 
 
92465f3
 
865139d
 
 
11110ab
865139d
 
 
11110ab
3d09a96
ba0e382
6ff8c22
4688078
29e90a0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
import streamlit as st
from openai import OpenAI
from os import getenv
from audiorecorder import audiorecorder
import tempfile
import base64
from pydub import AudioSegment
import os
import io

# Configuration du client OpenAI avec la clé API
client = OpenAI(api_key=getenv("OPENAI_API_KEY"))

# Fonction pour lire et retourner le contenu de fichiers textes
def lire_fichier(nom_fichier):
    try:
        with open(nom_fichier, 'r', encoding='utf-8') as fichier:
            contenu = fichier.read()
        return contenu
    except FileNotFoundError:
        return f"Erreur : Le fichier '{nom_fichier}' n'a pas été trouvé."
    except Exception as e:
        return f"Une erreur s'est produite lors de la lecture du fichier : {str(e)}"

# Fonction pour diviser un fichier audio en segments de 25 Mo ou moins
def split_audio(audio_file, max_size_mb=25):
    audio = AudioSegment.from_wav(audio_file)
    duration_ms = len(audio)
    segment_duration_ms = int((max_size_mb * 1024 * 1024 * 8) / (audio.frame_rate * audio.sample_width * audio.channels))
    
    segments = []
    for start in range(0, duration_ms, segment_duration_ms):
        end = min(start + segment_duration_ms, duration_ms)
        segment = audio[start:end]
        with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_segment:
            segment.export(temp_segment.name, format="wav")
            segments.append(temp_segment.name)
    
    return segments

# Fonction modifiée pour transcrire l'audio en texte
def transcribe_audio(audio_file, language=None):
    max_size_mb = 25
    file_size_mb = os.path.getsize(audio_file.name) / (1024 * 1024)
    
    if file_size_mb > max_size_mb:
        segments = split_audio(audio_file.name, max_size_mb)
        full_transcript = ""
        for segment in segments:
            with open(segment, "rb") as audio_segment:
                transcript = client.audio.transcriptions.create(
                    model="whisper-1", 
                    file=audio_segment,
                    language=language
                )
                full_transcript += transcript.text + " "
            os.unlink(segment)  # Supprimer le fichier temporaire
        return full_transcript.strip()
    else:
        with open(audio_file.name, "rb") as audio_file:
            transcript = client.audio.transcriptions.create(
                model="whisper-1", 
                file=audio_file,
                language=language
            )
        return transcript.text

# Fonction pour détecter la langue d'un texte donné
def language_detection(input_text, temperature=0.01):
    system_prompt = "".join([
        "Je souhaite que vous agissiez en tant que fonction linguistique.",
        "Je m'exprimerai dans n'importe quelle langue, et vous en détecterez la langue.",
        "Vous fournirez le résultat de votre détection au format ISO-639-1.",
        "Votre réponse doit représenter l'argument `language` et contenir seulement sa valeur de type chaîne de caractères.",
        "La langue de l'audio d'entrée. Fournir la langue d'entrée au format ISO-639-1 améliorera la précision et la latence."
    ])
    response = client.chat.completions.create(
        model="gpt-4o",
        temperature=temperature,
        messages=[
            {
                "role": "system",
                "content": system_prompt
            },
            {
                "role": "user",
                "content": f"{input_text}"
            }
        ]
    )
    return response.choices[0].message.content

def get_duration_pydub(audio_file):
    audio = AudioSegment.from_file(audio_file)
    return audio.duration_seconds

# Fonction pour convertir du texte en parole
def text_to_speech(text):
    response = client.audio.speech.create(
        model="tts-1",
        voice=st.session_state.tts_voice,
        input=text
    )
    
    # Sauvegarder l'audio dans un fichier temporaire
    with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as temp_audio:
        response.stream_to_file(temp_audio.name)
        
        # Lire le contenu du fichier audio
        with open(temp_audio.name, "rb") as audio_file:
            audio_bytes = audio_file.read()

        # Lire la durée de l'audio en secondes
        audio_duration = get_duration_pydub(temp_audio.name)

    return audio_bytes, audio_duration

def concatenate_audio_files(audio_list):
    # Créer un segment audio vide
    final_audio = AudioSegment.empty()
    
    # Charger les effets sonores
    begin_sound = AudioSegment.from_mp3("sound-effects/voice-message-play-begin/voice-message-play-begin-1.mp3")
    end_sound = AudioSegment.from_mp3("sound-effects/voice-message-play-ending/voice-message-play-ending-1.mp3")
    
    # Silence de 5 secondes
    silence = AudioSegment.silent(duration=5000)  # 5000 ms = 5 secondes
    
    for audio_bytes, _ in audio_list:
        # Convertir les bytes en un segment audio
        segment = AudioSegment.from_mp3(io.BytesIO(audio_bytes))
        
        # Ajouter le son de début, le segment TTS, le son de fin, et le silence au final_audio
        final_audio += begin_sound + segment + end_sound + silence
    
    # Convertir le segment audio final en bytes
    buffer = io.BytesIO()
    final_audio.export(buffer, format="mp3")
    return buffer.getvalue()

# Fonction pour traiter les messages de l'utilisateur et générer une réponse
def process_message(message, 
                    operation_prompt="", 
                    tts_enabled=False):
    payload_content = f'{operation_prompt} :\n\"\"\"\n{message}\n\"\"\"'

    st.session_state.messages.append({"role": "user", "content": payload_content})
    with st.chat_message("user"):
        st.markdown(message)

    with st.chat_message("assistant"):
        message_placeholder = st.empty()
        full_response = ""
        for response in client.chat.completions.create(
            model="gpt-4o",
            messages=st.session_state.messages,
            stream=True,
            temperature=0.1,
        ):
            full_response += (response.choices[0].delta.content or "")
            message_placeholder.markdown(full_response + "▌")
        message_placeholder.markdown(full_response)
    
    st.session_state.messages.append({"role": "assistant", "content": full_response})

    if tts_enabled:
        tts_audio, tts_duration = text_to_speech(full_response)
        return tts_audio, tts_duration
    return None, None
    #st.audio(tts_audio, format="audio/mp3", autoplay=True)


# Classe pour stocker les prompts système globaux
class GlobalSystemPrompts:
    # Méthode pour récupérer le prompt système pour la fonctionnalité Linguascribe
    def linguascribe():
        SYSTEM_PROMPT = f"{lire_fichier('linguascribe.prompt')}"
        return SYSTEM_PROMPT

# Variables globales pour les prompts
SYSTEM_PROMPT=""
OP_PROMPT=""

# Fonction pour configurer le mode de traduction
def set_mode_translation(from_lang, dest_lang):
    global SYSTEM_PROMPT
    global OP_PROMPT
    SYSTEM_PROMPT=GlobalSystemPrompts.linguascribe()
    OP_PROMPT = f"Translate({from_lang} to {dest_lang})"

# Liste des langues supportées par l'application
SUPPORTED_LANGUAGES=["Afrikaans", "Arabic", "Armenian", "Azerbaijani", "Belarusian", "Bosnian", "Bulgarian", "Catalan", "Chinese", "Croatian", "Czech", "Danish", "Dutch", "English", "Estonian", "Finnish", "French", "Galician", "German", "Greek", "Hebrew", "Hindi", "Hungarian", "Icelandic", "Indonesian", "Italian", "Japanese", "Kannada", "Kazakh", "Korean", "Latvian", "Lithuanian", "Macedonian", "Malay", "Marathi", "Maori", "Nepali", "Norwegian", "Persian", "Polish", "Portuguese", "Romanian", "Russian", "Serbian", "Slovak", "Slovenian", "Spanish", "Swahili", "Swedish", "Tagalog", "Tamil", "Thai", "Turkish", "Ukrainian", "Urdu", "Vietnamese", "Welsh"]

# Fonction pour convertir le nom d'une langue en code ISO 639-1
def convert_language_name_to_iso6391(language_data):
    # Dictionnaire de correspondance entre les noms de langues et les codes ISO 639-1
    language_to_iso = {
        "Afrikaans": "af", "Arabic": "ar", "Armenian": "hy", "Azerbaijani": "az",
        "Belarusian": "be", "Bosnian": "bs", "Bulgarian": "bg", "Catalan": "ca",
        "Chinese": "zh", "Croatian": "hr", "Czech": "cs", "Danish": "da",
        "Dutch": "nl", "English": "en", "Estonian": "et", "Finnish": "fi",
        "French": "fr", "Galician": "gl", "German": "de", "Greek": "el",
        "Hebrew": "he", "Hindi": "hi", "Hungarian": "hu", "Icelandic": "is",
        "Indonesian": "id", "Italian": "it", "Japanese": "ja", "Kannada": "kn",
        "Kazakh": "kk", "Korean": "ko", "Latvian": "lv", "Lithuanian": "lt",
        "Macedonian": "mk", "Malay": "ms", "Marathi": "mr", "Maori": "mi",
        "Nepali": "ne", "Norwegian": "no", "Persian": "fa", "Polish": "pl",
        "Portuguese": "pt", "Romanian": "ro", "Russian": "ru", "Serbian": "sr",
        "Slovak": "sk", "Slovenian": "sl", "Spanish": "es", "Swahili": "sw",
        "Swedish": "sv", "Tagalog": "tl", "Tamil": "ta", "Thai": "th",
        "Turkish": "tr", "Ukrainian": "uk", "Urdu": "ur", "Vietnamese": "vi",
        "Welsh": "cy"
    }
    
    # Vérifier si language_data est un dictionnaire
    if isinstance(language_data, dict):
        language_name = language_data.get('language')
    else:
        language_name = language_data

    # Retourne le code ISO 639-1 correspondant au nom de la langue
    return language_to_iso.get(language_name, "en")  # Par défaut, retourne 'en' si la langue n'est pas trouvée

# Fonction principale de l'application
def main():
    st.title("------- DEMORRHA -------")

    # Initialisation des variables d'état de la session
    if "language_detected" not in st.session_state:
        st.session_state["language_detected"] = None

    if "process_mode" not in st.session_state:
        st.session_state["process_mode"] = "translation"

    if "target_language" not in st.session_state:
        st.session_state.target_language = "en"

    if "selected_languages" not in st.session_state:
        st.session_state.selected_languages = [{"language": "English", "iso-639-1": "en"}]

    if "enable_tts_for_input_from_text_field" not in st.session_state:
        st.session_state["enable_tts_for_input_from_text_field"] = True

    if "enable_tts_for_input_from_audio_record" not in st.session_state:
        st.session_state["enable_tts_for_input_from_audio_record"] = True

    def init_process_mode():
        # Configuration du mode de traduction si nécessaire
        if "translation" == st.session_state["process_mode"]:
            set_mode_translation(from_lang=st.session_state.language_detected, dest_lang=st.session_state.target_language)
        

    init_process_mode()

    # Initialisation de l'historique des messages avec le prompt système
    if "messages" not in st.session_state:
        st.session_state.messages = []

    # Vérifier si un message système existe déjà dans st.session_state.messages
    if not any(message["role"] == "system" for message in st.session_state.messages):
        st.session_state.messages.insert(0, {"role": "system", "content": SYSTEM_PROMPT})

    # Interface utilisateur pour le chat textuel
    if user_input := st.chat_input("Entrez votre message ici:"):
        # Traitement du message textuel de l'utilisateur
        if None == st.session_state.language_detected:
            st.session_state.language_detected = language_detection(input_text=user_input, temperature=0.01)
                
        audio_list = []
        for cursor_selected_lang in st.session_state.selected_languages:
            st.session_state.target_language = cursor_selected_lang["iso-639-1"]
            
            # Initialisation du mode de traitement pour la langue cible actuelle
            init_process_mode()

            # Traitement du message de l'utilisateur pour la langue cible actuelle
            tts_audio, tts_duration = process_message( 
                                        user_input, 
                                        operation_prompt=f"{OP_PROMPT}", 
                                        tts_enabled=st.session_state.enable_tts_for_input_from_text_field
                                        )
            if tts_audio is not None:
                audio_list.append((tts_audio, tts_duration))
            
        if audio_list:
            final_audio = concatenate_audio_files(audio_list)
            st.audio(final_audio, 
                     format="audio/mp3", 
                     autoplay=True)
            
        # #################################################################
        # Affichage de l'historique des messages (sauf le message système)
        for message in st.session_state.messages[1:]:
            with st.chat_message(message["role"]):
                st.markdown(message["content"])

    with st.container(border=True):
        # Interface utilisateur pour l'enregistrement audio
        st.write("Ou enregistrez votre message audio :")
        audio = audiorecorder(start_prompt="Cliquez pour enregistrer", 
                              stop_prompt="Cliquez pour arrêter l'enregistrement", 
                              pause_prompt="Cliquez pour mettre en pause", 
                              show_visualizer=True, 
                              key="vocal_chat_input"
        )


    # Traitement de l'entrée audio de l'utilisateur
    if len(audio) > 0:
        with tempfile.NamedTemporaryFile(delete=False, suffix=".wav") as temp_audio:
            audio.export(temp_audio.name, format="wav")
            transcription = transcribe_audio(temp_audio, language=st.session_state.language_detected)
        os.unlink(temp_audio.name)  # Supprimer le fichier temporaire
        if None == st.session_state.language_detected:
            st.session_state.language_detected = language_detection(input_text=transcription, temperature=0.01)
            st.write(f"Langue détectée : {st.session_state.language_detected}")

        st.write(f"Transcription : {transcription}")

        audio_list = []
        for cursor_selected_lang in st.session_state.selected_languages:
            st.session_state.target_language = cursor_selected_lang["iso-639-1"]
            
            # Initialisation du mode de traitement pour la langue cible actuelle
            init_process_mode()
            
            # Traitement du message de l'utilisateur pour la langue cible actuelle
            tts_audio, tts_duration = process_message( 
                                        transcription, 
                                        operation_prompt=f"{OP_PROMPT}", 
                                        tts_enabled=st.session_state.enable_tts_for_input_from_audio_record
                                        )
            if tts_audio is not None:
                audio_list.append((tts_audio, tts_duration))
        if audio_list:
            final_audio = concatenate_audio_files(audio_list)
            st.audio(final_audio, 
                     format="audio/mp3",
                     autoplay=True)

    # Configuration de la barre latérale
    with st.sidebar:
        st.header("DEMORRHA - v1")
        st.markdown("## À propos")
        st.info("\n".join([
            "Cette application utilise Streamlit et l'API d'OpenAI pour créer un chat interactif avec des modèles de langages avancés dans le but de fournir un outil permettant la communication entre les êtres humains.",
            "Cet outil a pour objectif de montrer la voie dans un acte saint de la volonté de son auteur : ",
            "Abattre les barrières linguistiques entre les hommes."
        ]))

        # 
        def tts_voice_selection_change():
            sound_effect = "sound-effects/setting-tts-voice-select.mp3"
            # Jouer le son d'effet lors du changement de voix TTS
            audio_file = open(sound_effect, "rb")
            audio_bytes = audio_file.read()
            with st.sidebar:
                #st.markdown(
                #    f'<audio id=\"tts_voice_change_sound\" autoplay currentTime=\"0\" style=\"display:none\"><source src=\"data:audio/mp3;base64,{base64.b64encode(audio_bytes).decode()}\" type=\"audio/mp3\"></audio>',
                #    unsafe_allow_html=True
                #)
                st.audio(audio_bytes,
                         format="audio/mp3",
                         autoplay=True)
            
            audio_file.close()

        # Fonction de rappel pour le changement de(s) langue(s) de destination selectionnée(s)
        def on_languages_change():

            selected_language_names = st.session_state.language_selector
            st.session_state.selected_languages = [
                {"language": lang, "iso-639-1": convert_language_name_to_iso6391(lang)}
                for lang in selected_language_names
            ]
        
        with st.container(border=True):
            # Conteneur pour la sélection de la langue
            st.subheader("Sélection de la langue")

 
            # Sélection multiple des langues de destination
            st.multiselect(
                label="Choisissez les langues de destination",
                placeholder="Sélectionnez une a quatre langue(s)",
                options=SUPPORTED_LANGUAGES,
                default=["English"],
                key="language_selector",
                max_selections=4,
                on_change=on_languages_change
            )

        with st.container(border=True):
            st.subheader("Paramètres TTS")
            st.selectbox(
                "Choisissez la voix TTS",
                options=["alloy", "echo", "fable", "onyx", "nova", "shimmer"],
                index=3,  # "onyx" est à l'index 3
                key="tts_voice",
                on_change=tts_voice_selection_change
            )
            st.checkbox(
                "Activer TTS pour les entrées textuelles",
                key="enable_tts_for_input_from_text_field"
            )
            st.checkbox(
                "Activer TTS pour les entrées audio",
                key="enable_tts_for_input_from_audio_record"
            )    

# Point d'entrée de l'application
if __name__ == "__main__":
    main()