Spaces:
Running
Running
import gradio as gr | |
import torch | |
from transformers import pipeline, AutoModelForSeq2SeqLM, AutoTokenizer | |
import os | |
import time | |
from pathlib import Path | |
import docx | |
import PyPDF2 | |
import html2text | |
from bs4 import BeautifulSoup | |
import logging | |
import re | |
import random | |
# Configuration du logging | |
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s') | |
logger = logging.getLogger(__name__) | |
# Définition des paires de langues et des modèles correspondants | |
LANGUAGE_PAIRS = { | |
"Français → Arabe": {"src": "fr", "tgt": "ar", "model": "Helsinki-NLP/opus-mt-fr-ar"}, | |
"Arabe → Français": {"src": "ar", "tgt": "fr", "model": "Helsinki-NLP/opus-mt-ar-fr"}, | |
"Français → Anglais": {"src": "fr", "tgt": "en", "model": "Helsinki-NLP/opus-mt-fr-en"}, | |
"Anglais → Français": {"src": "en", "tgt": "fr", "model": "Helsinki-NLP/opus-mt-en-fr"}, | |
"Arabe → Anglais": {"src": "ar", "tgt": "en", "model": "Helsinki-NLP/opus-mt-ar-en"}, | |
"Anglais → Arabe": {"src": "en", "tgt": "ar", "model": "Helsinki-NLP/opus-mt-en-ar"} | |
} | |
# Création du dossier temporaire | |
TEMP_DIR = Path("temp") | |
TEMP_DIR.mkdir(exist_ok=True) | |
# Cache pour les modèles chargés (chargement paresseux) | |
loaded_models = {} | |
loaded_tokenizers = {} | |
# Nouvelle limite de caractères - 1 million | |
MAX_CHARS = 1000000 | |
MAX_FILE_SIZE_MB = 20 | |
# CSS personnalisé avec système de thèmes | |
custom_css = """ | |
/* Système de thèmes avec variables CSS */ | |
:root { | |
--primary-gradient: linear-gradient(90deg, #6366f1, #8b5cf6, #d946ef); | |
--secondary-gradient: linear-gradient(90deg, #3b82f6, #2dd4bf); | |
--primary-color: #6366f1; | |
--secondary-color: #8b5cf6; | |
--accent-color: #d946ef; | |
--text-primary: #1e293b; | |
--text-secondary: #475569; | |
--text-light: #f8fafc; | |
--bg-primary: #ffffff; | |
--bg-secondary: #f8fafc; | |
--bg-tertiary: #f1f5f9; | |
--shadow-color: rgba(0, 0, 0, 0.1); | |
--card-bg: #ffffff; | |
--border-color: #e2e8f0; | |
--success-color: #10b981; | |
--warning-color: #f59e0b; | |
--error-color: #ef4444; | |
} | |
/* Thème sombre */ | |
[data-theme="dark"] { | |
--primary-gradient: linear-gradient(90deg, #6366f1, #8b5cf6, #d946ef); | |
--secondary-gradient: linear-gradient(90deg, #3b82f6, #2dd4bf); | |
--primary-color: #818cf8; | |
--secondary-color: #a78bfa; | |
--accent-color: #e879f9; | |
--text-primary: #f8fafc; | |
--text-secondary: #cbd5e1; | |
--text-light: #f8fafc; | |
--bg-primary: #1e1e2d; | |
--bg-secondary: #171723; | |
--bg-tertiary: #262636; | |
--shadow-color: rgba(0, 0, 0, 0.3); | |
--card-bg: #262636; | |
--border-color: #393953; | |
--success-color: #34d399; | |
--warning-color: #fbbf24; | |
--error-color: #f87171; | |
} | |
/* Styles généraux */ | |
body { | |
background-color: var(--bg-secondary) !important; | |
color: var(--text-primary) !important; | |
transition: background-color 0.3s ease, color 0.3s ease; | |
} | |
.gradio-container { | |
background-color: var(--bg-secondary) !important; | |
} | |
/* Bouton de thème */ | |
#theme-toggle { | |
position: fixed; | |
top: 20px; | |
right: 20px; | |
z-index: 1000; | |
background: var(--primary-gradient); | |
color: white; | |
border: none; | |
border-radius: 50%; | |
width: 40px; | |
height: 40px; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
cursor: pointer; | |
box-shadow: 0 4px 6px var(--shadow-color); | |
transition: transform 0.3s ease; | |
} | |
#theme-toggle:hover { | |
transform: scale(1.1); | |
} | |
/* En-tête */ | |
.main-header { | |
background-image: var(--primary-gradient); | |
margin: -24px -16px 24px -16px; | |
padding: 40px 24px; | |
color: white; | |
text-align: center; | |
border-radius: 0 0 30px 30px; | |
box-shadow: 0 20px 30px -10px rgba(99, 102, 241, 0.3); | |
} | |
/* Badge diamant */ | |
.diamond-badge { | |
display: inline-block; | |
padding: 6px 12px; | |
background: linear-gradient(135deg, rgba(255, 255, 255, 0.1), rgba(255, 255, 255, 0.2)); | |
border-radius: 10px; | |
color: white; | |
font-weight: bold; | |
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); | |
margin-top: 10px; | |
border: 1px solid rgba(255, 255, 255, 0.3); | |
backdrop-filter: blur(10px); | |
} | |
/* Boutons */ | |
.fancy-button { | |
background: var(--primary-gradient) !important; | |
border: none !important; | |
color: white !important; | |
font-weight: 600 !important; | |
transition: all 0.3s ease !important; | |
box-shadow: 0 4px 12px -2px rgba(99, 102, 241, 0.4) !important; | |
border-radius: 12px !important; | |
padding: 12px 20px !important; | |
letter-spacing: 0.5px; | |
} | |
.fancy-button:hover { | |
transform: translateY(-2px); | |
box-shadow: 0 10px 15px -3px rgba(99, 102, 241, 0.4) !important; | |
filter: brightness(1.1); | |
} | |
/* Textes informatifs */ | |
.info-text { | |
color: var(--secondary-color); | |
font-style: italic; | |
font-size: 0.9em; | |
} | |
/* Badge Pro */ | |
.pro-badge { | |
background: linear-gradient(135deg, var(--warning-color), #d97706); | |
color: white; | |
padding: 2px 10px; | |
border-radius: 20px; | |
font-size: 0.7em; | |
font-weight: bold; | |
text-transform: uppercase; | |
margin-left: 10px; | |
vertical-align: middle; | |
box-shadow: 0 2px 4px rgba(245, 158, 11, 0.5); | |
} | |
/* Animation de pulsation */ | |
@keyframes pulse { | |
0% { box-shadow: 0 0 0 0 rgba(217, 70, 239, 0.7); } | |
70% { box-shadow: 0 0 0 10px rgba(217, 70, 239, 0); } | |
100% { box-shadow: 0 0 0 0 rgba(217, 70, 239, 0); } | |
} | |
.pulse-animation { | |
animation: pulse 2s infinite; | |
} | |
/* Pied de page */ | |
.footer { | |
margin-top: 40px; | |
padding: 20px; | |
text-align: center; | |
font-size: 0.9em; | |
color: var(--text-secondary); | |
background: linear-gradient(180deg, var(--bg-secondary), var(--bg-tertiary)); | |
border-radius: 20px; | |
box-shadow: 0 -4px 6px -1px var(--shadow-color); | |
} | |
/* Liste avec emojis */ | |
.emoji-list li { | |
list-style-type: none; | |
margin-bottom: 12px; | |
padding-left: 30px; | |
position: relative; | |
color: var(--text-primary); | |
} | |
.emoji-list li:before { | |
content: "✨"; | |
position: absolute; | |
left: 0; | |
top: 2px; | |
} | |
/* Container de document */ | |
.document-container { | |
background: var(--card-bg); | |
border-radius: 16px; | |
box-shadow: 0 4px 6px -1px var(--shadow-color), 0 2px 4px -1px var(--shadow-color); | |
padding: 24px; | |
margin-bottom: 24px; | |
border: 1px solid var(--border-color); | |
} | |
/* Adaptations du style pour les composants Gradio */ | |
.gr-form, | |
.gr-box, | |
.gr-panel { | |
background-color: var(--card-bg) !important; | |
border-color: var(--border-color) !important; | |
} | |
.gr-input, | |
.gr-textarea, | |
.gr-dropdown, | |
.gr-checkbox, | |
.gr-radio { | |
background-color: var(--bg-primary) !important; | |
border-color: var(--border-color) !important; | |
color: var(--text-primary) !important; | |
} | |
.gr-input:focus, | |
.gr-textarea:focus { | |
border-color: var(--primary-color) !important; | |
} | |
.gr-button-primary { | |
background-color: var(--primary-color) !important; | |
color: white !important; | |
} | |
.gr-button-secondary { | |
background-color: var(--bg-tertiary) !important; | |
color: var(--text-primary) !important; | |
} | |
/* Tabs */ | |
.tabs, .tab-nav button { | |
background-color: var(--bg-secondary) !important; | |
color: var(--text-primary) !important; | |
} | |
.tab-nav button.selected { | |
border-color: var(--accent-color) !important; | |
color: var(--primary-color) !important; | |
} | |
/* Cards */ | |
.prose { | |
background-color: var(--card-bg) !important; | |
color: var(--text-primary) !important; | |
} | |
/* Textes dans l'ensemble de l'interface */ | |
.prose h1, .prose h2, .prose h3, | |
.prose h4, .prose h5, .prose h6, | |
.prose p, .prose ul, .prose ol { | |
color: var(--text-primary) !important; | |
} | |
.prose a { | |
color: var(--primary-color) !important; | |
} | |
""" | |
def get_model_and_tokenizer(model_name): | |
"""Charge un modèle et tokenizer ou réutilise s'il est déjà chargé""" | |
if model_name not in loaded_models: | |
logger.info(f"Chargement du modèle: {model_name}") | |
loaded_models[model_name] = AutoModelForSeq2SeqLM.from_pretrained(model_name) | |
loaded_tokenizers[model_name] = AutoTokenizer.from_pretrained(model_name) | |
return loaded_models[model_name], loaded_tokenizers[model_name] | |
def segment_text(text, max_length=500): | |
"""Divise le texte en segments plus petits pour la traduction""" | |
paragraphs = text.split('\n\n') | |
segments = [] | |
for para in paragraphs: | |
if len(para) <= max_length: | |
segments.append(para) | |
else: | |
sentences = re.split(r'([.!?])\s+', para) | |
current = "" | |
for i in range(0, len(sentences), 2): | |
sentence = sentences[i] | |
if i + 1 < len(sentences): | |
sentence += sentences[i + 1] | |
if len(current) + len(sentence) > max_length: | |
if current: | |
segments.append(current) | |
current = sentence | |
else: | |
segments.append(sentence) | |
else: | |
current += " " + sentence if current else sentence | |
if current: | |
segments.append(current) | |
return [seg for seg in segments if seg.strip()] | |
def translate_text(text, language_pair): | |
"""Traduit du texte selon la paire de langues sélectionnée""" | |
if not text or not text.strip(): | |
return "✨ Veuillez entrer du texte à traduire. ✨" | |
# Vérifier la limite de caractères | |
if len(text) > MAX_CHARS: | |
return f"⚠️ Le texte dépasse la limite de {MAX_CHARS:,} caractères (1 million). Veuillez diviser votre document." | |
try: | |
# Extraire la paire de langues sans l'icône | |
lang_pair = language_pair | |
if "🇫🇷" in language_pair or "🇩🇿" in language_pair or "🇬🇧" in language_pair: | |
lang_pair = language_pair.split(" 🇫🇷")[0].split(" 🇩🇿")[0].split(" 🇬🇧")[0] | |
if lang_pair not in LANGUAGE_PAIRS: | |
return f"❌ Paire de langues non supportée: {lang_pair}" | |
pair_info = LANGUAGE_PAIRS[lang_pair] | |
model_name = pair_info["model"] | |
# Obtenir le modèle et le tokenizer | |
model, tokenizer = get_model_and_tokenizer(model_name) | |
# Pour les textes longs, diviser en segments | |
if len(text) > 500: | |
segments = segment_text(text) | |
translated_segments = [] | |
for segment in segments: | |
if segment.strip(): | |
# Tokeniser le texte | |
inputs = tokenizer(segment, return_tensors="pt", padding=True, truncation=True, max_length=512) | |
# Générer la traduction | |
outputs = model.generate(**inputs, max_length=512) | |
# Décoder la sortie | |
translation = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0] | |
translated_segments.append(translation) | |
else: | |
translated_segments.append("") | |
translated_text = "\n\n".join(translated_segments) | |
else: | |
# Pour les textes courts, traduction en une seule fois | |
inputs = tokenizer(text, return_tensors="pt", padding=True, truncation=True, max_length=512) | |
outputs = model.generate(**inputs, max_length=512) | |
translated_text = tokenizer.batch_decode(outputs, skip_special_tokens=True)[0] | |
return translated_text | |
except Exception as e: | |
logger.error(f"Erreur de traduction: {str(e)}") | |
return f"❌ Erreur lors de la traduction: {str(e)}" | |
def extract_text_from_file(file_path): | |
"""Extrait le texte d'un fichier selon son format""" | |
file_ext = Path(file_path).suffix.lower() | |
try: | |
if file_ext == ".txt": | |
with open(file_path, 'r', encoding='utf-8', errors='replace') as f: | |
return f.read() | |
elif file_ext == ".docx": | |
doc = docx.Document(file_path) | |
return "\n".join([para.text for para in doc.paragraphs if para.text]) | |
elif file_ext == ".pdf": | |
text = "" | |
with open(file_path, 'rb') as f: | |
pdf_reader = PyPDF2.PdfReader(f) | |
for page in pdf_reader.pages: | |
text += page.extract_text() + "\n" | |
return text | |
elif file_ext in [".html", ".htm"]: | |
with open(file_path, 'r', encoding='utf-8', errors='replace') as f: | |
html_content = f.read() | |
converter = html2text.HTML2Text() | |
converter.ignore_links = False | |
return converter.handle(html_content) | |
else: | |
return f"Format de fichier non supporté: {file_ext}" | |
except Exception as e: | |
logger.error(f"Erreur lors de l'extraction du texte: {str(e)}") | |
return f"Erreur lors de l'extraction du texte: {str(e)}" | |
def create_translated_document(original_path, translated_text, output_path): | |
"""Crée un document traduit selon le format d'origine""" | |
# Convertir les chemins en objets Path si ce sont des chaînes | |
if isinstance(original_path, str): | |
original_path = Path(original_path) | |
if isinstance(output_path, str): | |
output_path = Path(output_path) | |
file_ext = original_path.suffix.lower() | |
try: | |
if file_ext == ".txt": | |
with open(output_path, 'w', encoding='utf-8') as f: | |
f.write(translated_text) | |
elif file_ext == ".docx": | |
doc = docx.Document() | |
for paragraph in translated_text.split('\n'): | |
if paragraph.strip(): | |
doc.add_paragraph(paragraph) | |
doc.save(str(output_path)) | |
elif file_ext == ".pdf": | |
# Pour les PDF, on crée un document Word | |
doc = docx.Document() | |
for paragraph in translated_text.split('\n'): | |
if paragraph.strip(): | |
doc.add_paragraph(paragraph) | |
# Sauvegarder en format docx | |
output_path = str(output_path).replace('.pdf', '.docx') | |
doc.save(output_path) | |
elif file_ext in [".html", ".htm"]: | |
# Préparer le contenu HTML | |
html_content = translated_text.replace('\n', '<br>') | |
with open(output_path, 'w', encoding='utf-8') as f: | |
f.write(f"""<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="UTF-8"> | |
<title>Document Traduit</title> | |
</head> | |
<body> | |
{html_content} | |
</body> | |
</html>""") | |
# Toujours retourner une chaîne de caractères, pas un objet Path | |
return str(output_path) | |
except Exception as e: | |
logger.error(f"Erreur lors de la création du document traduit: {str(e)}") | |
return None | |
def translate_document(file, language_pair): | |
"""Traduit un document complet""" | |
if file is None: | |
return None, "📁 Veuillez téléverser un fichier." | |
# Vérifier la taille du fichier | |
if os.path.getsize(file.name) > MAX_FILE_SIZE_MB * 1024 * 1024: | |
return None, f"⚠️ Le fichier dépasse la limite de {MAX_FILE_SIZE_MB} Mo." | |
try: | |
# Créer des chemins temporaires (en tant que chaînes de caractères) | |
temp_input = str(TEMP_DIR / f"input_{int(time.time())}_{os.path.basename(file.name)}") | |
temp_output = str(TEMP_DIR / f"output_{int(time.time())}_{os.path.basename(file.name)}") | |
# Copier le fichier téléversé | |
with open(file.name, 'rb') as src, open(temp_input, 'wb') as dst: | |
dst.write(src.read()) | |
# Extraire le texte | |
extracted_text = extract_text_from_file(temp_input) | |
# Vérifier si l'extraction a réussi | |
if extracted_text.startswith("Erreur") or extracted_text.startswith("Format"): | |
return None, extracted_text | |
# Extraire la paire de langues sans l'icône | |
lang_pair = language_pair | |
if "🇫🇷" in language_pair or "🇩🇿" in language_pair or "🇬🇧" in language_pair: | |
lang_pair = language_pair.split(" 🇫🇷")[0].split(" 🇩🇿")[0].split(" 🇬🇧")[0] | |
# Traduire le texte | |
translated_text = translate_text(extracted_text, lang_pair) | |
# Vérifier si la traduction a réussi | |
if translated_text.startswith("❌") or translated_text.startswith("⚠️"): | |
return None, translated_text | |
# Créer le document traduit | |
output_path = create_translated_document(temp_input, translated_text, temp_output) | |
if output_path is None: | |
return None, "❌ Erreur lors de la création du document traduit." | |
# S'assurer que le chemin est une chaîne de caractères, pas un objet Path | |
if not isinstance(output_path, str): | |
output_path = str(output_path) | |
return output_path, f"✅ Traduction terminée! Document de {len(extracted_text):,} caractères traduit avec succès." | |
except Exception as e: | |
logger.error(f"Erreur lors de la traduction du document: {str(e)}") | |
return None, f"❌ Erreur lors de la traduction du document: {str(e)}" | |
def estimate_quality(text): | |
"""Estime la qualité de la traduction (simulée)""" | |
if not text or text.startswith("✨ Veuillez") or text.startswith("❌"): | |
return {"Excellente": 0, "Bonne": 0, "Moyenne": 0, "À améliorer": 0} | |
# Simuler une estimation de qualité | |
length = len(text) | |
if length > 100: | |
quality = {"Excellente": 0.85 + random.random() * 0.15, "Bonne": 0.15 - random.random() * 0.15, "Moyenne": 0, "À améliorer": 0} | |
else: | |
quality = {"Excellente": 0.7 + random.random() * 0.2, "Bonne": 0.3 - random.random() * 0.2, "Moyenne": 0, "À améliorer": 0} | |
return quality | |
def process_translation(text, language_pair): | |
"""Traduit le texte et estime la qualité""" | |
translated = translate_text(text, language_pair) | |
quality = estimate_quality(translated) | |
return translated, quality | |
def clear_text(): | |
"""Efface le texte d'entrée""" | |
return "", {"Excellente": 0, "Bonne": 0, "Moyenne": 0, "À améliorer": 0} | |
# Interface Gradio avec style amélioré et système de thèmes | |
with gr.Blocks(css=custom_css, title="✨ UltraTranslate ✨") as demo: | |
# Script JavaScript pour le changement de thème | |
gr.HTML(""" | |
<button id="theme-toggle">🌙</button> | |
<script> | |
// Ce script s'exécutera une fois que la page sera chargée | |
document.addEventListener('DOMContentLoaded', function() { | |
const html = document.documentElement; | |
const themeToggle = document.getElementById('theme-toggle'); | |
const savedTheme = localStorage.getItem('theme') || 'light'; | |
// Appliquer le thème sauvegardé | |
document.documentElement.setAttribute('data-theme', savedTheme); | |
themeToggle.innerHTML = savedTheme === 'dark' ? '☀️' : '🌙'; | |
// Fonction pour basculer le thème | |
function toggleTheme() { | |
const currentTheme = html.getAttribute('data-theme'); | |
const newTheme = currentTheme === 'dark' ? 'light' : 'dark'; | |
html.setAttribute('data-theme', newTheme); | |
localStorage.setItem('theme', newTheme); | |
themeToggle.innerHTML = newTheme === 'dark' ? '☀️' : '🌙'; | |
} | |
// Ajouter l'événement de clic au bouton | |
themeToggle.addEventListener('click', toggleTheme); | |
}); | |
</script> | |
""") | |
# En-tête personnalisé | |
gr.HTML(""" | |
<div class="main-header"> | |
<h1 style="font-size: 3em; margin-bottom: 0;">✨ UltraTranslate ✨</h1> | |
<p style="font-size: 1.4em; opacity: 0.9;">La puissance ultime de traduction entre le français, l'arabe et l'anglais</p> | |
<div class="diamond-badge">ÉDITION ULTRA PREMIUM</div> | |
</div> | |
""") | |
with gr.Tabs() as tabs: | |
# Onglet de traduction de texte | |
with gr.Tab("✏️ Traduction de texte", id=1): | |
gr.HTML('<div class="document-container">') | |
with gr.Row(): | |
with gr.Column(): | |
input_text = gr.Textbox( | |
label="Texte à traduire", | |
lines=14, | |
placeholder="Entrez votre texte ici et découvrez la magie de traduction ultra-puissante...", | |
) | |
with gr.Row(): | |
text_language_pair = gr.Dropdown( | |
choices=list(LANGUAGE_PAIRS.keys()), | |
label="Direction de traduction", | |
value="Français → Arabe", | |
) | |
# Note explicative stylisée avec badge Pro | |
gr.HTML('<p class="info-text">✨ Capacité révolutionnaire jusqu\'à 1 million de caractères! <span class="pro-badge">Pro</span></p>') | |
gr.HTML('<p class="info-text">🚀 Traduisez jusqu\'à 200 pages de texte en un clic!</p>') | |
# Exemples de traduction | |
example_inputs = [ | |
["Bonjour, comment allez-vous aujourd'hui? J'espère que vous passez une excellente journée.", "Français → Arabe"], | |
["مرحبا، كيف حالك اليوم؟ أتمنى أن تقضي يوما رائعا.", "Arabe → Français"], | |
["Hello, how are you today? I hope you're having a great day.", "Anglais → Français"] | |
] | |
gr.Examples( | |
examples=example_inputs, | |
inputs=[input_text, text_language_pair], | |
) | |
with gr.Row(): | |
clear_btn = gr.Button("Effacer", variant="secondary") | |
translate_btn = gr.Button("✨ Traduire ✨", elem_classes=["fancy-button", "pulse-animation"]) | |
with gr.Column(): | |
output_text = gr.Textbox( | |
label="Traduction", | |
lines=14, | |
) | |
# Indicateur de qualité (simulé) | |
with gr.Row(): | |
quality_indicator = gr.Label( | |
label="Qualité estimée", | |
value={"Excellente": 0.9, "Bonne": 0.1, "Moyenne": 0, "À améliorer": 0}, | |
show_label=True, | |
) | |
gr.HTML('</div>') | |
# Connecter les fonctions aux événements | |
translate_btn.click( | |
fn=process_translation, | |
inputs=[input_text, text_language_pair], | |
outputs=[output_text, quality_indicator] | |
) | |
clear_btn.click( | |
fn=clear_text, | |
inputs=[], | |
outputs=[input_text, quality_indicator] | |
) | |
# Onglet de traduction de documents | |
with gr.Tab("📄 Traduction de documents", id=2): | |
# Utiliser un div avec classe CSS au lieu de gr.Box | |
gr.HTML('<div class="document-container">') | |
with gr.Row(): | |
with gr.Column(): | |
input_file = gr.File( | |
label="Document à traduire", | |
file_types=[".txt", ".docx", ".pdf", ".html", ".htm"] | |
) | |
# Note explicative stylisée avec badge Pro | |
gr.HTML('<p class="info-text">✨ Formats supportés: TXT, DOCX, PDF, HTML <span class="pro-badge">Ultra</span></p>') | |
gr.HTML('<p class="info-text">🚀 Limite augmentée à 20 Mo! Jusqu\'à 200+ pages!</p>') | |
doc_language_pair = gr.Dropdown( | |
choices=list(LANGUAGE_PAIRS.keys()), | |
label="Direction de traduction", | |
value="Français → Arabe", | |
) | |
translate_doc_btn = gr.Button("✨ Traduire le document ✨", elem_classes=["fancy-button"]) | |
with gr.Column(): | |
output_file = gr.File(label="Document traduit") | |
status_text = gr.Textbox(label="Statut", lines=2) | |
gr.HTML('</div>') # Fermeture du div container | |
# Connecter la fonction à l'événement | |
translate_doc_btn.click( | |
fn=translate_document, | |
inputs=[input_file, doc_language_pair], | |
outputs=[output_file, status_text] | |
) | |
# Informations sur l'application | |
with gr.Accordion("✨ À propos d'UltraTranslate", open=False): | |
gr.HTML(""" | |
<div class="document-container"> | |
<h2 style="color: var(--primary-color); margin-top: 0; font-weight: 800;">✨ UltraTranslate - Édition Premium</h2> | |
<p style="font-size: 1.1em;">Cette application ultra-premium offre une puissance de traduction inégalée entre le français, l'arabe et l'anglais avec des capacités révolutionnaires.</p> | |
<h3 style="color: var(--secondary-color); margin-top: 30px;">Caractéristiques Ultra-Premium</h3> | |
<ul class="emoji-list"> | |
<li>Traduction de texte jusqu'à 1 million de caractères</li> | |
<li>Traduction de documents (TXT, DOCX, PDF, HTML) jusqu'à 20 Mo</li> | |
<li>Support pour documents de plus de 200 pages</li> | |
<li>Interface élégante et intuitive avec modes clair et sombre</li> | |
</ul> | |
</div> | |
""") | |
# Pied de page élégant | |
gr.HTML(""" | |
<div class="footer"> | |
<p style="font-size: 1.2em; font-weight: 600; color: var(--primary-color);">✨ UltraTranslate - La révolution de la traduction ✨</p> | |
<p>Édition Ultra-Premium développée avec passion</p> | |
<p style="font-size: 0.8em; margin-top: 15px;">Capacité jusqu'à 1 million de caractères • Traduction de documents jusqu'à 200+ pages</p> | |
</div> | |
""") | |
# Lancement de l'application Gradio | |
demo.launch() |