Spaces:
Sleeping
Sleeping
Commit
·
e102b16
1
Parent(s):
8b2823e
init
Browse files- .gitattributes +1 -0
- .gitignore +0 -1
- Dockerfile +28 -0
- README.md +5 -5
- app.py +0 -285
- assets/chatbot.jpg +0 -0
- assets/user.jpg +0 -0
- inference/__init__.py +0 -3
- inference/mistral.py +0 -53
- inference/nllb.py +0 -98
- inference/parle_tts.py +0 -197
- requirements.txt +0 -10
- styles.css +0 -172
- utils.py +0 -13
.gitattributes
CHANGED
|
@@ -18,6 +18,7 @@
|
|
| 18 |
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
*.pickle filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 21 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 23 |
*.rar filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 18 |
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
*.rar filter=lfs diff=lfs merge=lfs -text
|
.gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
| 1 |
-
.idea
|
|
|
|
|
|
Dockerfile
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.11-slim
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 5 |
+
git \
|
| 6 |
+
&& apt-get clean \
|
| 7 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 8 |
+
|
| 9 |
+
RUN useradd -m -u 1000 user
|
| 10 |
+
USER user
|
| 11 |
+
ENV HOME="/home/user"
|
| 12 |
+
ENV PATH="${HOME}/.local/bin:$PATH"
|
| 13 |
+
WORKDIR $HOME/app
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
# --- ✅ Cloner le repo privé avec authentification
|
| 18 |
+
# --- https://huggingface.co/docs/hub/en/spaces-sdks-docker#secrets-and-variables-management
|
| 19 |
+
RUN --mount=type=secret,id=GITHUB_TOKEN,mode=0444,required=true \
|
| 20 |
+
git clone https://sawadogosalif:$(cat /run/secrets/GITHUB_TOKEN)@github.com/BurkimbIA/spaces.git
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
RUN cp -r leaderboards/mt/* .
|
| 24 |
+
|
| 25 |
+
RUN pip install -r requirements.txt
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
CMD ["python", "app.py"]
|
README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: red
|
| 5 |
colorTo: green
|
| 6 |
-
sdk:
|
| 7 |
-
sdk_version: 5.34.2
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
|
|
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Moore MT Leaderboard
|
| 3 |
+
emoji: 🚗
|
| 4 |
colorFrom: red
|
| 5 |
colorTo: green
|
| 6 |
+
sdk: docker
|
|
|
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
| 9 |
+
short_description: Text2text Machine Translation for Moore language
|
| 10 |
---
|
| 11 |
|
| 12 |
+
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
DELETED
|
@@ -1,285 +0,0 @@
|
|
| 1 |
-
import re
|
| 2 |
-
import gradio as gr
|
| 3 |
-
import spaces
|
| 4 |
-
from inference import NLLBTranslator, MistralTranslator, ParlerTTSGenerator
|
| 5 |
-
from utils import load_css
|
| 6 |
-
from loguru import logger
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
translator_nllb = NLLBTranslator()
|
| 12 |
-
translator_mistral = MistralTranslator("sawadogosalif/SaChi_by_Mistral")
|
| 13 |
-
tts_generator = ParlerTTSGenerator("burkimbia/BIA-ParlerTTS-mini")
|
| 14 |
-
|
| 15 |
-
@spaces.GPU(duration=15)
|
| 16 |
-
def translate_text(message, model_choice, src_lang, tgt_lang):
|
| 17 |
-
"""Fonction de traduction"""
|
| 18 |
-
|
| 19 |
-
if model_choice == "SaChi-MT0.5 (NLLB)":
|
| 20 |
-
translator = translator_nllb
|
| 21 |
-
else:
|
| 22 |
-
translator = translator_mistral
|
| 23 |
-
|
| 24 |
-
return translator.translate(
|
| 25 |
-
text=message,
|
| 26 |
-
src_lang=src_lang,
|
| 27 |
-
tgt_lang=tgt_lang
|
| 28 |
-
)
|
| 29 |
-
|
| 30 |
-
@spaces.GPU(duration=15)
|
| 31 |
-
def tts_speech(text, speaker_choice):
|
| 32 |
-
"""Fonction de génération de la synthèse vocale"""
|
| 33 |
-
|
| 34 |
-
sample_rate, audio_arr = tts_generator.generate_speech(
|
| 35 |
-
text,
|
| 36 |
-
speaker_type=speaker_choice
|
| 37 |
-
)
|
| 38 |
-
|
| 39 |
-
if audio_arr is not None:
|
| 40 |
-
return sample_rate, audio_arr
|
| 41 |
-
return None
|
| 42 |
-
|
| 43 |
-
@spaces.GPU(duration=30)
|
| 44 |
-
def response_with_tts(message, history, model_choice, direction_choice, enable_tts, speaker_choice):
|
| 45 |
-
"""Fonction de réponse avec traduction et TTS optionnel"""
|
| 46 |
-
|
| 47 |
-
if direction_choice == "French to Moore":
|
| 48 |
-
src_lang, tgt_lang = "fra_Latn", "moor_Latn"
|
| 49 |
-
else:
|
| 50 |
-
src_lang, tgt_lang = "moor_Latn", "fra_Latn"
|
| 51 |
-
|
| 52 |
-
try:
|
| 53 |
-
translated_text = translate_text(message, model_choice, src_lang, tgt_lang)
|
| 54 |
-
except Exception as e:
|
| 55 |
-
logger.error(f"Erreur de traduction: {str(e)}")
|
| 56 |
-
return f"Erreur de traduction: {str(e)}", None
|
| 57 |
-
|
| 58 |
-
# Génération TTS si activée et si on traduit vers le mooré
|
| 59 |
-
audio_output = None
|
| 60 |
-
if enable_tts and direction_choice == "French to Moore" and translated_text:
|
| 61 |
-
try:
|
| 62 |
-
logger.info("Génération de la synthèse vocale...")
|
| 63 |
-
tts_result = tts_speech(translated_text, speaker_choice)
|
| 64 |
-
|
| 65 |
-
if tts_result is not None:
|
| 66 |
-
audio_output = tts_result
|
| 67 |
-
logger.info("Synthèse vocale générée avec succès")
|
| 68 |
-
else:
|
| 69 |
-
logger.warning("Échec de la génération TTS")
|
| 70 |
-
|
| 71 |
-
except Exception as e:
|
| 72 |
-
logger.error(f"Erreur TTS: {str(e)}")
|
| 73 |
-
|
| 74 |
-
return translated_text, audio_output
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
# Thème personnalisé
|
| 78 |
-
theme = gr.themes.Soft(
|
| 79 |
-
primary_hue=gr.themes.colors.purple,
|
| 80 |
-
secondary_hue=gr.themes.colors.blue,
|
| 81 |
-
neutral_hue=gr.themes.colors.slate,
|
| 82 |
-
font=gr.themes.GoogleFont("Inter"),
|
| 83 |
-
radius_size=gr.themes.sizes.radius_lg,
|
| 84 |
-
)
|
| 85 |
-
|
| 86 |
-
with gr.Blocks(theme=theme, css=load_css(), title="SaChi Multi-Modèles Demo") as demo:
|
| 87 |
-
|
| 88 |
-
# En-tête principal
|
| 89 |
-
with gr.Row(elem_classes="main-container"):
|
| 90 |
-
with gr.Column():
|
| 91 |
-
gr.HTML("""
|
| 92 |
-
<div class="header-section">
|
| 93 |
-
<h1 style="margin: 0; font-size: 2.5em; font-weight: 700;">
|
| 94 |
-
🎯 SaChi Multi-Modèles Demo avec Synthèse Vocale
|
| 95 |
-
</h1>
|
| 96 |
-
<div style="margin-top: 20px;">
|
| 97 |
-
<div class="feature-item" style="justify-content: center;">
|
| 98 |
-
<span class="feature-icon">🔄</span>
|
| 99 |
-
<span>Traduction bidirectionnelle Français ↔ Mooré</span>
|
| 100 |
-
</div>
|
| 101 |
-
<div class="feature-item" style="justify-content: center;">
|
| 102 |
-
<span class="feature-icon">🗣️</span>
|
| 103 |
-
<span>Synthèse vocale en mooré avec différentes voix</span>
|
| 104 |
-
</div>
|
| 105 |
-
</div>
|
| 106 |
-
<div style="margin-top: 25px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.2);">
|
| 107 |
-
<div style="display: flex; justify-content: center; gap: 30px; flex-wrap: wrap;">
|
| 108 |
-
<div class="model-item" style="color: rgba(255,255,255,0.9);">
|
| 109 |
-
<span class="feature-icon">📝</span>
|
| 110 |
-
<strong>Traduction:</strong> SaChi-MT0.5 (NLLB) / SaChi-MT1.5 (Mistral-Instruct)
|
| 111 |
-
</div>
|
| 112 |
-
<div class="model-item" style="color: rgba(255,255,255,0.9);">
|
| 113 |
-
<span class="feature-icon">🎤</span>
|
| 114 |
-
<strong>TTS:</strong> BIA-ParlerTTS-mini (mooré uniquement)
|
| 115 |
-
</div>
|
| 116 |
-
</div>
|
| 117 |
-
</div>
|
| 118 |
-
</div>
|
| 119 |
-
""")
|
| 120 |
-
|
| 121 |
-
# Interface principale
|
| 122 |
-
with gr.Row(elem_classes="main-container equal-height-row"):
|
| 123 |
-
# Colonne de gauche - Configuration
|
| 124 |
-
with gr.Column(scale=1, elem_classes="equal-height-column"):
|
| 125 |
-
with gr.Column(elem_classes="config-card"):
|
| 126 |
-
gr.HTML('<h2 class="section-title"><span class="section-icon">⚙️</span>Configuration</h2>')
|
| 127 |
-
|
| 128 |
-
model_choice = gr.Dropdown(
|
| 129 |
-
choices=["SaChi-MT0.5 (NLLB)", "SaChi-MT1.5 (Mistral-Instruct)"],
|
| 130 |
-
label="🤖 Modèle de traduction",
|
| 131 |
-
value="SaChi-MT1.5 (Mistral-Instruct)",
|
| 132 |
-
info="Choisissez le modèle de traduction",
|
| 133 |
-
container=True
|
| 134 |
-
)
|
| 135 |
-
|
| 136 |
-
direction_choice = gr.Dropdown(
|
| 137 |
-
choices=["French to Moore", "Moore to French"],
|
| 138 |
-
label="🔄 Direction de traduction",
|
| 139 |
-
value="French to Moore",
|
| 140 |
-
info="Sens de la traduction",
|
| 141 |
-
container=True
|
| 142 |
-
)
|
| 143 |
-
|
| 144 |
-
gr.HTML('<hr style="margin: 20px 0; border: none; border-top: 1px solid #e2e8f0;">')
|
| 145 |
-
|
| 146 |
-
enable_tts = gr.Checkbox(
|
| 147 |
-
label="🎤 Activer la synthèse vocale",
|
| 148 |
-
value=True,
|
| 149 |
-
info="Uniquement pour français → mooré"
|
| 150 |
-
)
|
| 151 |
-
|
| 152 |
-
speaker_choice = gr.Dropdown(
|
| 153 |
-
choices=["default", "male", "female"],
|
| 154 |
-
label="👤 Type de voix",
|
| 155 |
-
value="default",
|
| 156 |
-
info="Type de locuteur pour le TTS",
|
| 157 |
-
visible=True,
|
| 158 |
-
container=True
|
| 159 |
-
)
|
| 160 |
-
|
| 161 |
-
# Colonne de droite - Interface de traduction
|
| 162 |
-
with gr.Column(scale=2, elem_classes="equal-height-column"):
|
| 163 |
-
with gr.Column(elem_classes="translation-card"):
|
| 164 |
-
gr.HTML('<h2 class="section-title"><span class="section-icon">💬</span>Traduction</h2>')
|
| 165 |
-
|
| 166 |
-
text_input = gr.Textbox(
|
| 167 |
-
label="📝 Texte à traduire",
|
| 168 |
-
placeholder="Ex: Bonjour, comment allez-vous ?",
|
| 169 |
-
value="Bonjour, comment allez-vous ?",
|
| 170 |
-
lines=4,
|
| 171 |
-
max_lines=8,
|
| 172 |
-
container=True
|
| 173 |
-
)
|
| 174 |
-
|
| 175 |
-
with gr.Row():
|
| 176 |
-
translate_btn = gr.Button(
|
| 177 |
-
"🚀 Traduire",
|
| 178 |
-
variant="primary",
|
| 179 |
-
size="lg",
|
| 180 |
-
elem_classes="button-primary"
|
| 181 |
-
)
|
| 182 |
-
clear_btn = gr.Button(
|
| 183 |
-
"🗑️ Effacer",
|
| 184 |
-
variant="secondary",
|
| 185 |
-
elem_classes="button-secondary"
|
| 186 |
-
)
|
| 187 |
-
|
| 188 |
-
text_output = gr.Textbox(
|
| 189 |
-
label="📄 Traduction",
|
| 190 |
-
lines=4,
|
| 191 |
-
max_lines=8,
|
| 192 |
-
interactive=False,
|
| 193 |
-
container=True
|
| 194 |
-
)
|
| 195 |
-
|
| 196 |
-
generate_audio_btn = gr.Button(
|
| 197 |
-
"🎤 Générer l'audio",
|
| 198 |
-
variant="secondary",
|
| 199 |
-
size="lg",
|
| 200 |
-
visible=True,
|
| 201 |
-
elem_classes="button-secondary"
|
| 202 |
-
)
|
| 203 |
-
|
| 204 |
-
audio_output = gr.Audio(
|
| 205 |
-
label="🔊 Synthèse vocale (mooré)",
|
| 206 |
-
visible=True,
|
| 207 |
-
autoplay=False
|
| 208 |
-
)
|
| 209 |
-
|
| 210 |
-
# Fonctions de traitement
|
| 211 |
-
def translate_only(message, model_choice, direction_choice):
|
| 212 |
-
if direction_choice == "French to Moore":
|
| 213 |
-
src_lang, tgt_lang = "fra_Latn", "moor_Latn"
|
| 214 |
-
else:
|
| 215 |
-
src_lang, tgt_lang = "moor_Latn", "fra_Latn"
|
| 216 |
-
try:
|
| 217 |
-
translated_text = translate_text(message, model_choice, src_lang, tgt_lang)
|
| 218 |
-
return translated_text, gr.update(visible=direction_choice == "French to Moore")
|
| 219 |
-
except Exception as e:
|
| 220 |
-
logger.error(f"Erreur de traduction: {str(e)}")
|
| 221 |
-
return f"Erreur de traduction: {str(e)}", gr.update(visible=False)
|
| 222 |
-
|
| 223 |
-
def generate_audio(translated_text, speaker_choice):
|
| 224 |
-
try:
|
| 225 |
-
logger.info("Génération de la synthèse vocale...")
|
| 226 |
-
tts_result = tts_speech(translated_text, speaker_choice)
|
| 227 |
-
if tts_result is not None:
|
| 228 |
-
logger.info("Synthèse vocale générée avec succès")
|
| 229 |
-
return tts_result
|
| 230 |
-
else:
|
| 231 |
-
logger.warning("Échec de la génération TTS")
|
| 232 |
-
return None
|
| 233 |
-
except Exception as e:
|
| 234 |
-
logger.error(f"Erreur TTS: {str(e)}")
|
| 235 |
-
return None
|
| 236 |
-
|
| 237 |
-
def clear_fields():
|
| 238 |
-
return (
|
| 239 |
-
"Bonjour, comment allez-vous ?",
|
| 240 |
-
"",
|
| 241 |
-
None,
|
| 242 |
-
gr.update(visible=True)
|
| 243 |
-
)
|
| 244 |
-
|
| 245 |
-
def toggle_tts_options(direction, tts_enabled):
|
| 246 |
-
show_tts = (direction == "French to Moore" and tts_enabled)
|
| 247 |
-
return {
|
| 248 |
-
speaker_choice: gr.update(visible=show_tts),
|
| 249 |
-
audio_output: gr.update(visible=show_tts),
|
| 250 |
-
generate_audio_btn: gr.update(visible=show_tts)
|
| 251 |
-
}
|
| 252 |
-
|
| 253 |
-
# Événements
|
| 254 |
-
translate_btn.click(
|
| 255 |
-
fn=translate_only,
|
| 256 |
-
inputs=[text_input, model_choice, direction_choice],
|
| 257 |
-
outputs=[text_output, generate_audio_btn]
|
| 258 |
-
)
|
| 259 |
-
|
| 260 |
-
generate_audio_btn.click(
|
| 261 |
-
fn=generate_audio,
|
| 262 |
-
inputs=[text_output, speaker_choice],
|
| 263 |
-
outputs=[audio_output]
|
| 264 |
-
)
|
| 265 |
-
|
| 266 |
-
clear_btn.click(
|
| 267 |
-
fn=clear_fields,
|
| 268 |
-
inputs=[],
|
| 269 |
-
outputs=[text_input, text_output, audio_output, generate_audio_btn]
|
| 270 |
-
)
|
| 271 |
-
|
| 272 |
-
direction_choice.change(
|
| 273 |
-
fn=lambda d, t: toggle_tts_options(d, t),
|
| 274 |
-
inputs=[direction_choice, enable_tts],
|
| 275 |
-
outputs=[speaker_choice, audio_output, generate_audio_btn]
|
| 276 |
-
)
|
| 277 |
-
|
| 278 |
-
enable_tts.change(
|
| 279 |
-
fn=lambda t, d: toggle_tts_options(d, t),
|
| 280 |
-
inputs=[enable_tts, direction_choice],
|
| 281 |
-
outputs=[speaker_choice, audio_output, generate_audio_btn]
|
| 282 |
-
)
|
| 283 |
-
|
| 284 |
-
if __name__ == "__main__":
|
| 285 |
-
demo.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assets/chatbot.jpg
DELETED
|
Binary file (61.2 kB)
|
|
|
assets/user.jpg
DELETED
|
Binary file (2.66 kB)
|
|
|
inference/__init__.py
DELETED
|
@@ -1,3 +0,0 @@
|
|
| 1 |
-
from .mistral import MistralTranslator
|
| 2 |
-
from .nllb import NLLBTranslator
|
| 3 |
-
from .parle_tts import ParlerTTSGenerator
|
|
|
|
|
|
|
|
|
|
|
|
inference/mistral.py
DELETED
|
@@ -1,53 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import re
|
| 3 |
-
from peft import AutoPeftModelForCausalLM
|
| 4 |
-
from transformers import AutoTokenizer
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
class MistralTranslator:
|
| 8 |
-
"""
|
| 9 |
-
Wrapper around a Mistral-Instruct-based model fine-tuned for French↔Mooré translation.
|
| 10 |
-
"""
|
| 11 |
-
def __init__(self, model_id: str, hf_token_env: str = "HF_TOKEN"):
|
| 12 |
-
hf_token = os.environ.get(hf_token_env)
|
| 13 |
-
if hf_token is None:
|
| 14 |
-
raise ValueError(f"Please set the environment variable {hf_token_env} with your HuggingFace token.")
|
| 15 |
-
self.model = AutoPeftModelForCausalLM.from_pretrained(
|
| 16 |
-
model_id,
|
| 17 |
-
token=hf_token,
|
| 18 |
-
device_map="auto"
|
| 19 |
-
)
|
| 20 |
-
self.tokenizer = AutoTokenizer.from_pretrained(model_id)
|
| 21 |
-
self.prompt_template = (
|
| 22 |
-
"""
|
| 23 |
-
<s>
|
| 24 |
-
|
| 25 |
-
You are an expert Moore translator. Translate the provided {src_name} text to {tgt_name}.
|
| 26 |
-
The Moore alphabet is: a, ã, b, d, e, ẽ, ɛ, f, g, h, i, ĩ, ɩ, k, l, m, n, o, õ, p, r, s, t, u, ũ, ʋ, v, w, y, z.
|
| 27 |
-
Based on source language ({src_name}), provide the {tgt_name} text.
|
| 28 |
-
[INST]
|
| 29 |
-
### {src_name}:
|
| 30 |
-
{text}
|
| 31 |
-
[/INST]
|
| 32 |
-
|
| 33 |
-
### {tgt_name}:
|
| 34 |
-
"""
|
| 35 |
-
)
|
| 36 |
-
|
| 37 |
-
def translate(self, text: str, src_lang: str, tgt_lang: str) -> str:
|
| 38 |
-
lang_map = {"fra_Latn": "French", "moor_Latn": "Moore"}
|
| 39 |
-
src_name = lang_map.get(src_lang, src_lang)
|
| 40 |
-
tgt_name = lang_map.get(tgt_lang, tgt_lang)
|
| 41 |
-
prompt = self.prompt_template.format(src_name=src_name, tgt_name=tgt_name, text=text)
|
| 42 |
-
inputs = self.tokenizer(prompt, return_tensors="pt").to("cuda")
|
| 43 |
-
outputs = self.model.generate(
|
| 44 |
-
input_ids=inputs.input_ids,
|
| 45 |
-
attention_mask=inputs.attention_mask,
|
| 46 |
-
max_new_tokens=512,
|
| 47 |
-
do_sample=False
|
| 48 |
-
)
|
| 49 |
-
decoded = self.tokenizer.decode(outputs[0], skip_special_tokens=True)
|
| 50 |
-
pattern = rf"### {tgt_name}:\s*(.+)"
|
| 51 |
-
match = re.search(pattern, decoded, re.DOTALL)
|
| 52 |
-
return match.group(1).strip() if match else decoded
|
| 53 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inference/nllb.py
DELETED
|
@@ -1,98 +0,0 @@
|
|
| 1 |
-
import re
|
| 2 |
-
import os
|
| 3 |
-
import sys
|
| 4 |
-
import typing as tp
|
| 5 |
-
import unicodedata
|
| 6 |
-
|
| 7 |
-
import torch
|
| 8 |
-
from sacremoses import MosesPunctNormalizer
|
| 9 |
-
from transformers import AutoModelForSeq2SeqLM, NllbTokenizer
|
| 10 |
-
|
| 11 |
-
MODEL_URL = "sawadogosalif/SaChi-MT"
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
from huggingface_hub import login
|
| 15 |
-
auth_token = os.getenv('HF_TOKEN')
|
| 16 |
-
login(token=auth_token)
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
class TextPreprocessor:
|
| 23 |
-
"""
|
| 24 |
-
Mimic the text preprocessing made for the NLLB model.
|
| 25 |
-
This code is adapted from the Stopes repo of the NLLB team:
|
| 26 |
-
https://github.com/facebookresearch/stopes/blob/main/stopes/pipelines/monolingual/monolingual_line_processor.py#L214
|
| 27 |
-
"""
|
| 28 |
-
|
| 29 |
-
def __init__(self, lang="fr"):
|
| 30 |
-
self.mpn = MosesPunctNormalizer(lang=lang)
|
| 31 |
-
self.mpn.substitutions = [
|
| 32 |
-
(re.compile(r), sub) for r, sub in self.mpn.substitutions
|
| 33 |
-
]
|
| 34 |
-
|
| 35 |
-
def __call__(self, text: str) -> str:
|
| 36 |
-
clean = self.mpn.normalize(text)
|
| 37 |
-
clean = unicodedata.normalize("NFKC", clean)
|
| 38 |
-
clean = clean[0].lower() + clean[1:]
|
| 39 |
-
return clean
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
def fix_tokenizer(tokenizer, new_lang):
|
| 43 |
-
"""
|
| 44 |
-
Ajoute un nouveau token de langue au tokenizer et met à jour les mappings d’identifiants.
|
| 45 |
-
|
| 46 |
-
- Ajoute le token spécial s'il n'existe pas déjà.
|
| 47 |
-
- Initialise ou met à jour `lang_code_to_id` et `id_to_lang_code` en utilisant `getattr` pour éviter les vérifications répétitives.
|
| 48 |
-
"""
|
| 49 |
-
if new_lang not in tokenizer.additional_special_tokens:
|
| 50 |
-
tokenizer.add_special_tokens({'additional_special_tokens': [new_lang]})
|
| 51 |
-
|
| 52 |
-
tokenizer.lang_code_to_id = getattr(tokenizer, 'lang_code_to_id', {})
|
| 53 |
-
tokenizer.id_to_lang_code = getattr(tokenizer, 'id_to_lang_code', {})
|
| 54 |
-
|
| 55 |
-
if new_lang not in tokenizer.lang_code_to_id:
|
| 56 |
-
new_lang_id = tokenizer.convert_tokens_to_ids(new_lang)
|
| 57 |
-
tokenizer.lang_code_to_id[new_lang] = new_lang_id
|
| 58 |
-
tokenizer.id_to_lang_code[new_lang_id] = new_lang
|
| 59 |
-
|
| 60 |
-
return tokenizer
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
class NLLBTranslator:
|
| 65 |
-
def __init__(self):
|
| 66 |
-
self.model = AutoModelForSeq2SeqLM.from_pretrained(MODEL_URL)
|
| 67 |
-
if torch.cuda.is_available():
|
| 68 |
-
self.model.cuda()
|
| 69 |
-
self.tokenizer = NllbTokenizer.from_pretrained(MODEL_URL)
|
| 70 |
-
self.tokenizer = fix_tokenizer(self.tokenizer, "moor_Latn")
|
| 71 |
-
|
| 72 |
-
self.preprocessor = TextPreprocessor()
|
| 73 |
-
|
| 74 |
-
def translate(self, text, src_lang='fr_Latn', tgt_lang='moor_Latn', a=32, b=3, max_input_length=1024, num_beams=5, **kwargs):
|
| 75 |
-
# 🩹 temporary
|
| 76 |
-
tmp = tgt_lang
|
| 77 |
-
src_lang = tgt_lang
|
| 78 |
-
tgt_lang = tmp
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
self.tokenizer.src_lang = src_lang
|
| 82 |
-
self.tokenizer.tgt_lang = tgt_lang
|
| 83 |
-
text_clean = self.preprocessor(text)
|
| 84 |
-
|
| 85 |
-
inputs = self.tokenizer(text_clean, return_tensors='pt', padding=True, truncation=True, max_length=max_input_length)
|
| 86 |
-
result = self.model.generate(
|
| 87 |
-
**inputs.to(self.model.device),
|
| 88 |
-
forced_bos_token_id=self.tokenizer.convert_tokens_to_ids(tgt_lang),
|
| 89 |
-
max_new_tokens=int(a + b * inputs.input_ids.shape[1]),
|
| 90 |
-
num_beams=num_beams,
|
| 91 |
-
**kwargs
|
| 92 |
-
)
|
| 93 |
-
output = self.tokenizer.batch_decode(result, skip_special_tokens=True)[0]
|
| 94 |
-
if text.endswith('?') or text.endswith('!'):
|
| 95 |
-
output += text[-1] # Ajouter le dernier caractère (soit "?" ou "!")
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
return output.capitalize()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inference/parle_tts.py
DELETED
|
@@ -1,197 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
import torch
|
| 3 |
-
import numpy as np
|
| 4 |
-
from transformers import AutoTokenizer
|
| 5 |
-
from parler_tts import ParlerTTSForConditionalGeneration
|
| 6 |
-
from loguru import logger
|
| 7 |
-
|
| 8 |
-
from huggingface_hub import login
|
| 9 |
-
auth_token = os.getenv('HF_TOKEN')
|
| 10 |
-
login(token=auth_token)
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
class ParlerTTSGenerator:
|
| 14 |
-
"""Générateur TTS pour le mooré utilisant ParlerTTS"""
|
| 15 |
-
|
| 16 |
-
def __init__(self, model_checkpoint="burkimbia/BIA-ParlerTTS-mini"):
|
| 17 |
-
"""
|
| 18 |
-
Initialiser le générateur TTS
|
| 19 |
-
|
| 20 |
-
Args:
|
| 21 |
-
model_checkpoint (str): Nom du modèle HuggingFace
|
| 22 |
-
"""
|
| 23 |
-
self.model_checkpoint = model_checkpoint
|
| 24 |
-
self.device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 25 |
-
self.model = None
|
| 26 |
-
self.tokenizer = None
|
| 27 |
-
self.description_tokenizer = None
|
| 28 |
-
|
| 29 |
-
# Configuration par défaut pour la génération
|
| 30 |
-
self.gen_kwargs = {
|
| 31 |
-
'do_sample': True,
|
| 32 |
-
'temperature': 1.0,
|
| 33 |
-
'max_length': 2580,
|
| 34 |
-
'min_new_tokens': 10
|
| 35 |
-
}
|
| 36 |
-
|
| 37 |
-
# Descriptions des locuteurs par défaut
|
| 38 |
-
self.speaker_descriptions = {
|
| 39 |
-
"default": "Christian speaks very slowly but has an animated delivery in a room with background",
|
| 40 |
-
"male": "A male voice speaking in Moore language with calm intonation and clear pronunciation.",
|
| 41 |
-
"female": "A female voice speaking in Moore language with gentle tone and clear articulation."
|
| 42 |
-
}
|
| 43 |
-
|
| 44 |
-
self.current_speaker = "default"
|
| 45 |
-
|
| 46 |
-
def load_model(self):
|
| 47 |
-
"""Charger le modèle TTS de manière lazy"""
|
| 48 |
-
try:
|
| 49 |
-
if self.model is None:
|
| 50 |
-
logger.info(f"Chargement du modèle TTS: {self.model_checkpoint}")
|
| 51 |
-
|
| 52 |
-
self.model = ParlerTTSForConditionalGeneration.from_pretrained(
|
| 53 |
-
self.model_checkpoint,
|
| 54 |
-
use_auth_token=True, # Pour les repos privés
|
| 55 |
-
torch_dtype=torch.float16 if self.device == "cuda" else torch.float32
|
| 56 |
-
).to(self.device)
|
| 57 |
-
|
| 58 |
-
self.tokenizer = AutoTokenizer.from_pretrained(
|
| 59 |
-
self.model_checkpoint,
|
| 60 |
-
use_auth_token=True
|
| 61 |
-
)
|
| 62 |
-
|
| 63 |
-
self.description_tokenizer = AutoTokenizer.from_pretrained(
|
| 64 |
-
self.model_checkpoint,
|
| 65 |
-
use_auth_token=True
|
| 66 |
-
)
|
| 67 |
-
|
| 68 |
-
logger.info("Modèle TTS chargé avec succès")
|
| 69 |
-
|
| 70 |
-
except Exception as e:
|
| 71 |
-
logger.error(f"Erreur lors du chargement du modèle TTS: {str(e)}")
|
| 72 |
-
raise
|
| 73 |
-
|
| 74 |
-
def set_speaker(self, speaker_type="default"):
|
| 75 |
-
"""
|
| 76 |
-
Définir le type de locuteur
|
| 77 |
-
|
| 78 |
-
Args:
|
| 79 |
-
speaker_type (str): Type de locuteur ("default", "male", "female")
|
| 80 |
-
"""
|
| 81 |
-
if speaker_type in self.speaker_descriptions:
|
| 82 |
-
self.current_speaker = speaker_type
|
| 83 |
-
else:
|
| 84 |
-
logger.warning(f"Type de locuteur '{speaker_type}' non reconnu, utilisation de 'default'")
|
| 85 |
-
self.current_speaker = "default"
|
| 86 |
-
|
| 87 |
-
def set_custom_description(self, description):
|
| 88 |
-
"""
|
| 89 |
-
Définir une description personnalisée pour le locuteur
|
| 90 |
-
|
| 91 |
-
Args:
|
| 92 |
-
description (str): Description personnalisée du locuteur
|
| 93 |
-
"""
|
| 94 |
-
self.speaker_descriptions["custom"] = description
|
| 95 |
-
self.current_speaker = "custom"
|
| 96 |
-
|
| 97 |
-
def generate_speech(self, text, speaker_type=None):
|
| 98 |
-
"""
|
| 99 |
-
Générer la parole à partir du texte
|
| 100 |
-
|
| 101 |
-
Args:
|
| 102 |
-
text (str): Texte à synthétiser
|
| 103 |
-
speaker_type (str, optional): Type de locuteur à utiliser
|
| 104 |
-
|
| 105 |
-
Returns:
|
| 106 |
-
tuple: (sample_rate, audio_array) ou (None, None) en cas d'erreur
|
| 107 |
-
"""
|
| 108 |
-
try:
|
| 109 |
-
if self.model is None:
|
| 110 |
-
self.load_model()
|
| 111 |
-
|
| 112 |
-
if speaker_type:
|
| 113 |
-
self.set_speaker(speaker_type)
|
| 114 |
-
|
| 115 |
-
speaker_description = self.speaker_descriptions[self.current_speaker]
|
| 116 |
-
|
| 117 |
-
text = self._preprocess_text(text)
|
| 118 |
-
|
| 119 |
-
input_ids = self.description_tokenizer(
|
| 120 |
-
speaker_description,
|
| 121 |
-
return_tensors="pt",
|
| 122 |
-
padding=True,
|
| 123 |
-
truncation=True
|
| 124 |
-
).input_ids.to(self.device)
|
| 125 |
-
|
| 126 |
-
prompt_input_ids = self.tokenizer(
|
| 127 |
-
text,
|
| 128 |
-
return_tensors="pt",
|
| 129 |
-
padding=True,
|
| 130 |
-
truncation=True
|
| 131 |
-
).input_ids.to(self.device)
|
| 132 |
-
|
| 133 |
-
logger.info(f"Génération TTS pour: '{text[:50]}...'")
|
| 134 |
-
|
| 135 |
-
with torch.no_grad():
|
| 136 |
-
generation = self.model.generate(
|
| 137 |
-
input_ids=input_ids,
|
| 138 |
-
prompt_input_ids=prompt_input_ids,
|
| 139 |
-
**self.gen_kwargs
|
| 140 |
-
)
|
| 141 |
-
|
| 142 |
-
audio_arr = generation.cpu().numpy().squeeze()
|
| 143 |
-
sample_rate = self.model.config.sampling_rate
|
| 144 |
-
|
| 145 |
-
logger.info("Synthèse vocale réussie")
|
| 146 |
-
return sample_rate, audio_arr
|
| 147 |
-
|
| 148 |
-
except Exception as e:
|
| 149 |
-
logger.error(f"Erreur lors de la synthèse vocale: {str(e)}")
|
| 150 |
-
return None, None
|
| 151 |
-
|
| 152 |
-
def _preprocess_text(self, text):
|
| 153 |
-
"""
|
| 154 |
-
Préprocesser le texte avant la synthèse
|
| 155 |
-
|
| 156 |
-
Args:
|
| 157 |
-
text (str): Texte original
|
| 158 |
-
|
| 159 |
-
Returns:
|
| 160 |
-
str: Texte préprocessé
|
| 161 |
-
"""
|
| 162 |
-
# Nettoyer le texte si nécessaire
|
| 163 |
-
text = text.strip()
|
| 164 |
-
# to improve
|
| 165 |
-
return text
|
| 166 |
-
|
| 167 |
-
def is_model_loaded(self):
|
| 168 |
-
"""Vérifier si le modèle est chargé"""
|
| 169 |
-
return self.model is not None
|
| 170 |
-
|
| 171 |
-
def unload_model(self):
|
| 172 |
-
"""Décharger le modèle pour libérer la mémoire"""
|
| 173 |
-
if self.model is not None:
|
| 174 |
-
del self.model
|
| 175 |
-
del self.tokenizer
|
| 176 |
-
del self.description_tokenizer
|
| 177 |
-
self.model = None
|
| 178 |
-
self.tokenizer = None
|
| 179 |
-
self.description_tokenizer = None
|
| 180 |
-
|
| 181 |
-
if torch.cuda.is_available():
|
| 182 |
-
torch.cuda.empty_cache()
|
| 183 |
-
|
| 184 |
-
logger.info("Modèle TTS déchargé")
|
| 185 |
-
|
| 186 |
-
def get_available_speakers(self):
|
| 187 |
-
"""Obtenir la liste des types de locuteurs disponibles"""
|
| 188 |
-
return list(self.speaker_descriptions.keys())
|
| 189 |
-
|
| 190 |
-
def get_model_info(self):
|
| 191 |
-
"""Obtenir des informations sur le modèle"""
|
| 192 |
-
return {
|
| 193 |
-
"model_checkpoint": self.model_checkpoint,
|
| 194 |
-
"device": self.device,
|
| 195 |
-
"is_loaded": self.is_model_loaded(),
|
| 196 |
-
"available_speakers": self.get_available_speakers()
|
| 197 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
requirements.txt
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 1 |
-
accelerate==1.6.0
|
| 2 |
-
bitsandbytes==0.45.5
|
| 3 |
-
peft==0.15.0
|
| 4 |
-
gradio>=3.18.0
|
| 5 |
-
torch
|
| 6 |
-
loguru
|
| 7 |
-
sacremoses
|
| 8 |
-
sentencepiece
|
| 9 |
-
spaces
|
| 10 |
-
git+https://github.com/huggingface/parler-tts.git
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
styles.css
DELETED
|
@@ -1,172 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
.main-container {
|
| 3 |
-
max-width: 1200px;
|
| 4 |
-
margin: 0 auto;
|
| 5 |
-
padding: 20px;
|
| 6 |
-
}
|
| 7 |
-
|
| 8 |
-
.header-section {
|
| 9 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
| 10 |
-
color: white;
|
| 11 |
-
padding: 30px;
|
| 12 |
-
border-radius: 15px;
|
| 13 |
-
margin-bottom: 30px;
|
| 14 |
-
text-align: center;
|
| 15 |
-
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
| 16 |
-
}
|
| 17 |
-
|
| 18 |
-
.config-card {
|
| 19 |
-
background: white;
|
| 20 |
-
border-radius: 15px;
|
| 21 |
-
padding: 25px;
|
| 22 |
-
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
|
| 23 |
-
border: 1px solid #e2e8f0;
|
| 24 |
-
margin-bottom: 20px;
|
| 25 |
-
height: 100%;
|
| 26 |
-
display: flex;
|
| 27 |
-
flex-direction: column;
|
| 28 |
-
}
|
| 29 |
-
|
| 30 |
-
.translation-card {
|
| 31 |
-
background: white;
|
| 32 |
-
border-radius: 15px;
|
| 33 |
-
padding: 25px;
|
| 34 |
-
box-shadow: 0 5px 20px rgba(0,0,0,0.08);
|
| 35 |
-
border: 1px solid #e2e8f0;
|
| 36 |
-
height: 100%;
|
| 37 |
-
display: flex;
|
| 38 |
-
flex-direction: column;
|
| 39 |
-
}
|
| 40 |
-
|
| 41 |
-
/* Assurer que les colonnes ont la même hauteur */
|
| 42 |
-
.equal-height-row {
|
| 43 |
-
display: flex;
|
| 44 |
-
align-items: stretch;
|
| 45 |
-
}
|
| 46 |
-
|
| 47 |
-
.equal-height-column {
|
| 48 |
-
display: flex;
|
| 49 |
-
flex-direction: column;
|
| 50 |
-
}
|
| 51 |
-
|
| 52 |
-
.feature-item {
|
| 53 |
-
display: flex;
|
| 54 |
-
align-items: center;
|
| 55 |
-
margin: 10px 0;
|
| 56 |
-
font-size: 16px;
|
| 57 |
-
}
|
| 58 |
-
|
| 59 |
-
.feature-icon {
|
| 60 |
-
margin-right: 10px;
|
| 61 |
-
font-size: 18px;
|
| 62 |
-
}
|
| 63 |
-
|
| 64 |
-
.model-item {
|
| 65 |
-
display: flex;
|
| 66 |
-
align-items: center;
|
| 67 |
-
margin: 8px 0;
|
| 68 |
-
font-size: 14px;
|
| 69 |
-
color: #64748b;
|
| 70 |
-
}
|
| 71 |
-
|
| 72 |
-
.section-title {
|
| 73 |
-
font-size: 20px;
|
| 74 |
-
font-weight: 600;
|
| 75 |
-
margin-bottom: 20px;
|
| 76 |
-
color: #1e293b;
|
| 77 |
-
display: flex;
|
| 78 |
-
align-items: center;
|
| 79 |
-
}
|
| 80 |
-
|
| 81 |
-
.section-icon {
|
| 82 |
-
margin-right: 10px;
|
| 83 |
-
font-size: 22px;
|
| 84 |
-
}
|
| 85 |
-
|
| 86 |
-
.button-primary {
|
| 87 |
-
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
|
| 88 |
-
border: none !important;
|
| 89 |
-
color: white !important;
|
| 90 |
-
font-weight: 600 !important;
|
| 91 |
-
padding: 12px 24px !important;
|
| 92 |
-
border-radius: 8px !important;
|
| 93 |
-
transition: all 0.3s ease !important;
|
| 94 |
-
}
|
| 95 |
-
|
| 96 |
-
.button-primary:hover {
|
| 97 |
-
transform: translateY(-2px) !important;
|
| 98 |
-
box-shadow: 0 10px 25px rgba(102, 126, 234, 0.3) !important;
|
| 99 |
-
}
|
| 100 |
-
|
| 101 |
-
.button-secondary {
|
| 102 |
-
background: #f8fafc !important;
|
| 103 |
-
border: 2px solid #e2e8f0 !important;
|
| 104 |
-
color: #64748b !important;
|
| 105 |
-
font-weight: 600 !important;
|
| 106 |
-
padding: 12px 24px !important;
|
| 107 |
-
border-radius: 8px !important;
|
| 108 |
-
transition: all 0.3s ease !important;
|
| 109 |
-
}
|
| 110 |
-
|
| 111 |
-
.button-secondary:hover {
|
| 112 |
-
background: #f1f5f9 !important;
|
| 113 |
-
border-color: #cbd5e1 !important;
|
| 114 |
-
}
|
| 115 |
-
|
| 116 |
-
/* Styles responsifs */
|
| 117 |
-
@media (max-width: 768px) {
|
| 118 |
-
.main-container {
|
| 119 |
-
padding: 10px;
|
| 120 |
-
}
|
| 121 |
-
|
| 122 |
-
.header-section {
|
| 123 |
-
padding: 20px;
|
| 124 |
-
}
|
| 125 |
-
|
| 126 |
-
.config-card,
|
| 127 |
-
.translation-card {
|
| 128 |
-
padding: 20px;
|
| 129 |
-
}
|
| 130 |
-
|
| 131 |
-
.equal-height-row {
|
| 132 |
-
flex-direction: column;
|
| 133 |
-
}
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
/* Amélioration des composants Gradio */
|
| 137 |
-
.gradio-container {
|
| 138 |
-
font-family: 'Inter', sans-serif;
|
| 139 |
-
}
|
| 140 |
-
|
| 141 |
-
/* Styling pour les dropdowns */
|
| 142 |
-
.gr-dropdown {
|
| 143 |
-
border-radius: 8px !important;
|
| 144 |
-
border: 1px solid #e2e8f0 !important;
|
| 145 |
-
}
|
| 146 |
-
|
| 147 |
-
/* Styling pour les textboxes */
|
| 148 |
-
.gr-textbox {
|
| 149 |
-
border-radius: 8px !important;
|
| 150 |
-
border: 1px solid #e2e8f0 !important;
|
| 151 |
-
}
|
| 152 |
-
|
| 153 |
-
/* Styling pour les checkboxes */
|
| 154 |
-
.gr-checkbox {
|
| 155 |
-
border-radius: 4px !important;
|
| 156 |
-
}
|
| 157 |
-
|
| 158 |
-
/* Animation de chargement */
|
| 159 |
-
.loading-spinner {
|
| 160 |
-
display: inline-block;
|
| 161 |
-
width: 20px;
|
| 162 |
-
height: 20px;
|
| 163 |
-
border: 3px solid #f3f3f3;
|
| 164 |
-
border-top: 3px solid #667eea;
|
| 165 |
-
border-radius: 50%;
|
| 166 |
-
animation: spin 1s linear infinite;
|
| 167 |
-
}
|
| 168 |
-
|
| 169 |
-
@keyframes spin {
|
| 170 |
-
0% { transform: rotate(0deg); }
|
| 171 |
-
100% { transform: rotate(360deg); }
|
| 172 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
utils.py
DELETED
|
@@ -1,13 +0,0 @@
|
|
| 1 |
-
|
| 2 |
-
import os
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
def load_css():
|
| 6 |
-
"""Charge le fichier CSS externe"""
|
| 7 |
-
css_path = os.path.join(os.path.dirname(__file__), "styles.css")
|
| 8 |
-
try:
|
| 9 |
-
with open(css_path, "r", encoding="utf-8") as f:
|
| 10 |
-
return f.read()
|
| 11 |
-
except FileNotFoundError:
|
| 12 |
-
logger.warning(f"Fichier CSS non trouvé: {css_path}")
|
| 13 |
-
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|