import gradio as gr import os import re import time import nltk from pptx import Presentation from pptx.util import Inches, Pt from pptx.dml.color import RGBColor from pptx.enum.text import PP_ALIGN from pptx.enum.shapes import MSO_SHAPE import PyPDF2 from docx import Document from typing import List, Dict, Any import hashlib # ========================================== # PARTIE 1 : INTELLIGENCE ADAPTATIVE AVANCÉE # ========================================== class SmartAIEnhance: def __init__(self): try: nltk.download('punkt', quiet=True) nltk.download('punkt_tab', quiet=True) nltk.download('averaged_perceptron_tagger', quiet=True) nltk.download('maxent_ne_chunker', quiet=True) nltk.download('words', quiet=True) except: pass def detect_document_features(self, text: str) -> Dict[str, Any]: """Détecte les caractéristiques spécifiques du document""" features = { 'has_abstract': bool(re.search(r'(résumé|abstract|summary)', text[:1000], re.I)), 'has_methodology': bool(re.search(r'(méthodologie|methodology|méthode|method)', text, re.I)), 'has_results': bool(re.search(r'(résultats|results|findings)', text, re.I)), 'has_conclusion': bool(re.search(r'(conclusion|discussion)', text, re.I)), 'has_references': bool(re.search(r'(références|references|bibliographie)', text[-2000:], re.I)), 'has_figures': len(re.findall(r'(figure|fig\.|tableau|table)', text, re.I)) > 5, 'word_count': len(text.split()), 'sentence_count': len(nltk.sent_tokenize(text)) if nltk else len(text.split('.')) } return features def extract_metadata(self, text: str, category: str) -> Dict[str, Any]: """Extrait les métadonnées spécifiques au type de document""" metadata = { 'title': self._extract_title(text), 'authors': self._extract_authors(text) if category in ["Article Scientifique"] else [], 'date': self._extract_date(text), 'keywords': self._extract_keywords(text, category), 'main_findings': self._extract_findings(text, category) } return metadata def _extract_title(self, text: str) -> str: """Extrait le titre probable du document""" lines = text.split('\n') for line in lines[:10]: if len(line) > 20 and len(line) < 200 and line[0].isupper(): return line.strip() return nltk.sent_tokenize(text)[0][:100] if nltk else text[:100] def _extract_authors(self, text: str) -> List[str]: """Extrait les auteurs (pour articles scientifiques)""" author_pattern = r'(?:authors?|auteurs?):?\s*([^\n]+)' match = re.search(author_pattern, text[:2000], re.I) if match: authors = [a.strip() for a in match.group(1).split(',')] return authors[:5] return [] def _extract_date(self, text: str) -> str: """Extrait la date du document""" date_pattern = r'\b(0?[1-9]|[12][0-9]|3[01])[/-](0?[1-9]|1[012])[/-](19|20)\d{2}\b' match = re.search(date_pattern, text) return match.group(0) if match else time.strftime("%d/%m/%Y") def _extract_keywords(self, text: str, category: str) -> List[str]: """Extrait les mots-clés spécifiques au domaine""" # Recherche de section mots-clés kw_pattern = r'(?:mots[-\s]clés?|keywords?):?\s*([^\n]+)' match = re.search(kw_pattern, text[:2000], re.I) if match: return [k.strip() for k in match.group(1).split(',')[:6]] # Sinon, extraction des mots fréquents words = re.findall(r'\b[A-Za-z]{4,}\b', text.lower()) from collections import Counter common = Counter(words).most_common(10) return [w for w, c in common if c > 3][:6] def _extract_findings(self, text: str, category: str) -> List[str]: """Extrait les principales conclusions/découvertes""" finding_patterns = [ r'(?:conclusion|résultat|finding|我们发现|我们发现|我们发现)[s]?:\s*([^.]+)', r'(?:montre|indique|suggère|suggere|démontre|demontre)[^.]*\.', r'(?:important|significatif|crucial)[^.]*\.' ] findings = [] for pattern in finding_patterns: matches = re.findall(pattern, text, re.I) findings.extend([m.strip()[:100] for m in matches if len(m.strip()) > 20]) return findings[:5] def create_adaptive_outline(self, text: str, category: str) -> Dict[str, Any]: """Crée un plan adaptatif basé sur le type de document""" text = text.replace('\n', ' ').strip() features = self.detect_document_features(text) metadata = self.extract_metadata(text, category) # Plans spécifiques pour chaque type de document plans = { "Article Scientifique": { "title_slide": ["Titre", "Auteurs", "Affiliation", "Date"], "slides": [ {"title": "RÉSUMÉ & INTRODUCTION", "content_type": "abstract", "icon": "📄"}, {"title": "CONTEXTE & PROBLÉMATIQUE", "content_type": "context", "icon": "❓"}, {"title": "MÉTHODOLOGIE", "content_type": "methods", "icon": "🔬"}, {"title": "RÉSULTATS PRINCIPAUX", "content_type": "results", "icon": "📊"}, {"title": "DISCUSSION & ANALYSE", "content_type": "discussion", "icon": "💭"}, {"title": "CONCLUSIONS & PERSPECTIVES", "content_type": "conclusion", "icon": "🎯"}, {"title": "RÉFÉRENCES CLÉS", "content_type": "references", "icon": "📚"} ] }, "Communication Scientifique": { "title_slide": ["Titre", "Auteur", "Événement", "Date"], "slides": [ {"title": "CONTEXTE & ENJEUX", "content_type": "context", "icon": "🌍"}, {"title": "MESSAGE CLÉ", "content_type": "key_message", "icon": "💡"}, {"title": "DONNÉES PRINCIPALES", "content_type": "results", "icon": "📈"}, {"title": "IMPACT & APPLICATIONS", "content_type": "impact", "icon": "⚡"}, {"title": "CONCLUSION", "content_type": "conclusion", "icon": "🎯"}, {"title": "PERSPECTIVES", "content_type": "perspectives", "icon": "🔮"} ] }, "Rapport d'Étude": { "title_slide": ["Titre de l'étude", "Commanditaire", "Équipe", "Date"], "slides": [ {"title": "CADRE DE L'ÉTUDE", "content_type": "context", "icon": "📋"}, {"title": "OBJECTIFS & MÉTHODOLOGIE", "content_type": "methods", "icon": "🎯"}, {"title": "DONNÉES COLLECTÉES", "content_type": "data", "icon": "📊"}, {"title": "ANALYSE TECHNIQUE", "content_type": "analysis", "icon": "🔍"}, {"title": "CONSTATS PRINCIPAUX", "content_type": "findings", "icon": "📌"}, {"title": "RECOMMANDATIONS", "content_type": "recommendations", "icon": "💡"}, {"title": "AXES STRATÉGIQUES", "content_type": "strategic", "icon": "🎯"}, {"title": "ANNEXES TECHNIQUES", "content_type": "appendices", "icon": "📎"} ] }, "Rapport de Réunion": { "title_slide": ["Objet", "Date", "Lieu", "Participants"], "slides": [ {"title": "ORDRE DU JOUR", "content_type": "agenda", "icon": "📅"}, {"title": "PARTICIPANTS", "content_type": "participants", "icon": "👥"}, {"title": "POINTS DISCUTÉS", "content_type": "discussions", "icon": "💬"}, {"title": "DÉCISIONS PRISES", "content_type": "decisions", "icon": "✅"}, {"title": "PLAN D'ACTION", "content_type": "actions", "icon": "📋"}, {"title": "PROCHAINE RÉUNION", "content_type": "next", "icon": "⏰"} ] }, "Cours pour Étudiants": { "title_slide": ["Titre du cours", "Niveau", "Enseignant", "Date"], "slides": [ {"title": "OBJECTIFS PÉDAGOGIQUES", "content_type": "objectives", "icon": "🎓"}, {"title": "PLAN DU COURS", "content_type": "plan", "icon": "📑"}, {"title": "CONCEPTS CLÉS", "content_type": "concepts", "icon": "🔑"}, {"title": "EXEMPLES ILLUSTRATIFS", "content_type": "examples", "icon": "💡"}, {"title": "EXERCICES PRATIQUES", "content_type": "exercises", "icon": "✏️"}, {"title": "RÉSUMÉ", "content_type": "summary", "icon": "📌"}, {"title": "RÉFÉRENCES", "content_type": "references", "icon": "📚"}, {"title": "QUESTIONS/RÉPONSES", "content_type": "qa", "icon": "❓"} ] }, "Note de Synthèse": { "title_slide": ["Titre", "Destinataire", "Rédacteur", "Date"], "slides": [ {"title": "CONTEXTE", "content_type": "context", "icon": "🌍"}, {"title": "ENJEUX PRINCIPAUX", "content_type": "challenges", "icon": "⚠️"}, {"title": "ANALYSE SYNTHÉTIQUE", "content_type": "analysis", "icon": "📊"}, {"title": "POINTS D'ATTENTION", "content_type": "attention", "icon": "🔍"}, {"title": "RECOMMANDATIONS", "content_type": "recommendations", "icon": "💡"}, {"title": "SUITES À DONNER", "content_type": "next_steps", "icon": "⏩"} ] }, "Document de Travail": { "title_slide": ["Titre", "Groupe", "Version", "Date"], "slides": [ {"title": "CONTEXTE DU DOCUMENT", "content_type": "context", "icon": "📄"}, {"title": "OBJECTIFS VISÉS", "content_type": "objectives", "icon": "🎯"}, {"title": "ÉTAT D'AVANCEMENT", "content_type": "progress", "icon": "📈"}, {"title": "POINTS DE BLOCAGE", "content_type": "blockers", "icon": "🚧"}, {"title": "PROCHAINES ÉTAPES", "content_type": "next", "icon": "⏭️"}, {"title": "BESOINS", "content_type": "needs", "icon": "🆘"} ] } } # Sélection du plan approprié selected_plan = plans.get(category, plans["Rapport d'Étude"]) # Construction de l'outline outline = { "title": metadata['title'].upper(), "category": category, "metadata": metadata, "features": features, "slides": [] } # Slide de titre enrichi title_slide = { "type": "title", "title": outline["title"], "subtitle": f"LAB_MATH & LABHP | DIVISION PROSPECTIVE\n{category.upper()}", "metadata": metadata } outline["slides"].append(title_slide) # Slide de présentation du document outline["slides"].append({ "type": "document_info", "title": "INFORMATIONS SUR LE DOCUMENT", "metadata": metadata, "features": features }) # Slides de contenu adaptatifs sentences = nltk.sent_tokenize(text) if nltk else [s for s in text.split('.') if len(s) > 20] for slide_config in selected_plan["slides"]: content = self._extract_content_for_type(sentences, slide_config["content_type"], features) outline["slides"].append({ "type": "content", "icon": slide_config["icon"], "title": f"{slide_config['icon']} {slide_config['title']}", "content": content, "content_type": slide_config["content_type"] }) # Slide de remerciements multilingue outline["slides"].append({ "type": "thanks", "title": "MERCI POUR VOTRE ATTENTION", "content": [ "Thank you (EN)", "Gracias (ES)", "Danke (DE)", "شكراً (AR)", "Asante (SW)", "Merci beaucoup (FR)" ] }) return outline def _extract_content_for_type(self, sentences: List[str], content_type: str, features: Dict) -> List[str]: """Extrait le contenu spécifique selon le type de slide""" content_patterns = { "abstract": ["résumé", "abstract", "summary", "synthèse"], "context": ["contexte", "context", "introduction", "cadre"], "methods": ["méthodologie", "methodology", "méthode", "approach"], "results": ["résultat", "result", "finding", "montre"], "discussion": ["discussion", "analyse", "interpretation"], "conclusion": ["conclusion", "conclusion"], "objectives": ["objectif", "objective", "but", "goal"], "recommendations": ["recommandation", "recommendation", "préconisation"], "key_message": ["message", "key point", "essentiel"], "data": ["donnée", "data", "information", "collecte"], "analysis": ["analyse", "analysis", "étude"], "findings": ["constat", "finding", "observation"], "strategic": ["stratégique", "strategic", "axe"], "agenda": ["ordre du jour", "agenda", "programme"], "decisions": ["décision", "decision", "validation"], "actions": ["action", "task", "plan"], "concepts": ["concept", "notion", "definition"], "examples": ["exemple", "example", "illustration"], "exercises": ["exercice", "exercise", "pratique"], "participants": ["participant", "présent", "attendance"] } keywords = content_patterns.get(content_type, [content_type]) # Recherche de phrases pertinentes relevant = [] for sent in sentences[:50]: # Limiter pour performance if any(kw in sent.lower() for kw in keywords): # Nettoyage et formatage clean = sent.strip() if len(clean) > 150: clean = clean[:147] + "..." if len(clean) > 20: relevant.append(clean) # Si pas assez de contenu, prendre des phrases génériques if len(relevant) < 3: generic = [s for s in sentences if len(s) > 50][:5] relevant.extend(generic[:3-len(relevant)]) # Ajouter des points spécifiques selon les features détectées if content_type == "methods" and features.get('has_methodology'): relevant.append("✓ Méthodologie détaillée disponible dans le document source") elif content_type == "results" and features.get('has_results'): relevant.append("✓ Résultats significatifs présentés dans le document") elif content_type == "conclusion" and features.get('has_conclusion'): relevant.append("✓ Conclusion principale détaillée") return relevant[:5] # Maximum 5 points par slide # ========================================== # PARTIE 2 : DESIGN PREMIUM ADAPTATIF # ========================================== class PremiumGenerator: def __init__(self): self.colors = { "Article Scientifique": { "primary": RGBColor(0, 51, 102), "secondary": RGBColor(204, 153, 0), "accent": RGBColor(0, 102, 102) }, "Communication Scientifique": { "primary": RGBColor(102, 0, 51), "secondary": RGBColor(255, 153, 0), "accent": RGBColor(0, 102, 153) }, "Rapport d'Étude": { "primary": RGBColor(0, 76, 76), "secondary": RGBColor(204, 102, 0), "accent": RGBColor(76, 0, 76) }, "Rapport de Réunion": { "primary": RGBColor(51, 51, 102), "secondary": RGBColor(153, 153, 0), "accent": RGBColor(102, 51, 102) }, "Cours pour Étudiants": { "primary": RGBColor(0, 51, 102), "secondary": RGBColor(204, 102, 0), "accent": RGBColor(0, 102, 51) }, "Note de Synthèse": { "primary": RGBColor(51, 51, 51), "secondary": RGBColor(153, 0, 0), "accent": RGBColor(0, 51, 102) }, "Document de Travail": { "primary": RGBColor(102, 51, 0), "secondary": RGBColor(0, 102, 102), "accent": RGBColor(153, 0, 76) } } self.default_colors = { "primary": RGBColor(0, 32, 96), "secondary": RGBColor(197, 150, 30), "accent": RGBColor(0, 96, 96) } def get_colors(self, category): return self.colors.get(category, self.default_colors) def generate(self, outline): prs = Presentation() prs.slide_width, prs.slide_height = Inches(13.33), Inches(7.5) colors = self.get_colors(outline["category"]) for i, slide_data in enumerate(outline["slides"]): stype = slide_data["type"] # Layout adapté au type de slide if stype == "title": layout = prs.slide_layouts[0] elif stype == "document_info": layout = prs.slide_layouts[1] # Layout avec titre et contenu else: layout = prs.slide_layouts[1] slide = prs.slides.add_slide(layout) # Barre de titre colorée title_bar = slide.shapes.add_shape( MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, Inches(0.15) ) title_bar.fill.solid() title_bar.fill.fore_color.rgb = colors["primary"] title_bar.line.fill.background() # Titre principal title = slide.shapes.title title.text = slide_data["title"] title.text_frame.paragraphs[0].font.color.rgb = colors["primary"] title.text_frame.paragraphs[0].font.bold = True title.text_frame.paragraphs[0].font.size = Pt(32 if stype != "title" else 40) title.text_frame.paragraphs[0].alignment = PP_ALIGN.LEFT if stype == "title": # Slide de titre avec métadonnées sub = slide.placeholders[1] sub.text = slide_data["subtitle"] sub.text_frame.paragraphs[0].font.color.rgb = colors["secondary"] sub.text_frame.paragraphs[0].font.size = Pt(18) # Ajout des métadonnées si disponibles if slide_data.get("metadata"): metadata_text = f"\nAuteur(s): {', '.join(slide_data['metadata'].get('authors', ['Non spécifié']))}\n" metadata_text += f"Date: {slide_data['metadata'].get('date', 'Non spécifiée')}" sub.text += f"\n\n{metadata_text}" elif stype == "document_info": # Slide d'information sur le document body = slide.placeholders[1] tf = body.text_frame tf.clear() metadata = slide_data["metadata"] features = slide_data["features"] # Métadonnées formatées info_points = [ f"📌 Titre: {metadata.get('title', 'Non spécifié')}", f"👥 Auteurs: {', '.join(metadata.get('authors', ['Non spécifié']))}", f"📅 Date: {metadata.get('date', 'Non spécifiée')}", f"🔑 Mots-clés: {', '.join(metadata.get('keywords', ['Non spécifiés']))}", f"📊 Statistiques: {features.get('word_count', 0)} mots, {features.get('sentence_count', 0)} phrases", ] for point in info_points: p = tf.add_paragraph() p.text = point p.font.size = Pt(18) p.space_before = Pt(10) p.font.color.rgb = RGBColor(60, 60, 60) else: # Slides de contenu body = slide.placeholders[1] tf = body.text_frame tf.clear() tf.word_wrap = True for point in slide_data["content"]: p = tf.add_paragraph() p.text = f"• {point}" p.font.size = Pt(20) p.space_before = Pt(12) p.font.color.rgb = RGBColor(60, 60, 60) # Mise en évidence des mots importants if "important" in point.lower() or "clé" in point.lower(): p.font.bold = True p.font.color.rgb = colors["secondary"] # Numéro de slide if stype != "title": slide_num = slide.shapes.add_textbox( Inches(12), Inches(7.0), Inches(1), Inches(0.3) ) slide_num.text_frame.text = str(i+1) slide_num.text_frame.paragraphs[0].font.size = Pt(10) slide_num.text_frame.paragraphs[0].font.color.rgb = colors["accent"] # Footer avec catégorie footer = slide.shapes.add_textbox( Inches(0.5), Inches(7.1), Inches(4), Inches(0.3) ) footer.text_frame.text = f"LAB_MATH Prospective | {outline['category']}" footer.text_frame.paragraphs[0].font.size = Pt(8) footer.text_frame.paragraphs[0].font.italic = True # Sauvegarde filename = f"Presentation_{outline['category'].replace(' ', '_')}_{int(time.time())}.pptx" prs.save(filename) return filename # ========================================== # PARTIE 3 : LOGIQUE PRINCIPALE # ========================================== ai = SmartAIEnhance() gen = PremiumGenerator() ACCESS_KEY = "32015LabMath_1a" ADMIN_KEY = "admin123" # Pour la gestion def extract_text(file_obj): """Extrait le texte du fichier uploadé""" try: if file_obj.name.endswith('.pdf'): reader = PyPDF2.PdfReader(file_obj.name) text = " ".join([p.extract_text() for p in reader.pages if p.extract_text()]) elif file_obj.name.endswith('.docx'): doc = Document(file_obj.name) text = " ".join([p.text for p in doc.paragraphs]) elif file_obj.name.endswith('.txt'): with open(file_obj.name, 'r', encoding='utf-8', errors='ignore') as f: text = f.read() else: text = open(file_obj.name, 'r', encoding='utf-8', errors='ignore').read() # Nettoyage text = re.sub(r'\s+', ' ', text) return text.strip() except Exception as e: raise Exception(f"Erreur lors de l'extraction du texte: {str(e)}") def process(file, category, key): """Fonction principale de traitement""" # Vérification de la clé if key != ACCESS_KEY: return None, "❌ Clé d'accès invalide. Accès refusé." if not file: return None, "⚠️ Veuillez uploader un fichier." try: # Extraction du texte text = extract_text(file) if len(text) < 100: return None, "⚠️ Le document est trop court ou n'a pas pu être lu correctement." # Création du plan adaptatif outline = ai.create_adaptive_outline(text, category) # Génération du PowerPoint ppt_path = gen.generate(outline) return ppt_path, f"""✅ Succès ! Présentation générée avec succès. 📊 Type: {category} 📝 Slides: {len(outline['slides'])} ⏱️ Temps de traitement: {int(time.time() - time.time())}s La présentation est prête à être utilisée sans modifications supplémentaires.""" except Exception as e: return None, f"❌ Erreur lors du traitement: {str(e)}" def preview_outline(file, category, key): """Aperçu du plan avant génération""" if key != ACCESS_KEY: return "Clé invalide" if not file: return "Veuillez uploader un fichier" try: text = extract_text(file) outline = ai.create_adaptive_outline(text, category) preview = f"📋 APERÇU DU PLAN - {category}\n" preview += "=" * 50 + "\n\n" for i, slide in enumerate(outline["slides"]): if slide["type"] != "thanks": preview += f"{i+1}. {slide['title']}\n" if slide.get("content"): preview += f" → {len(slide['content'])} points\n" preview += f"\nTotal: {len(outline['slides'])} slides" return preview except Exception as e: return f"Erreur: {str(e)}" # ========================================== # INTERFACE UTILISATEUR AMÉLIORÉE # ========================================== with gr.Blocks(theme=gr.themes.Soft(), title="Lab_Math Premium Generator") as demo: gr.Markdown(""" # 🎯 Lab_Math Premium Slide Generator ### Générateur intelligent de présentations PowerPoint adaptatives """) with gr.Tabs(): with gr.TabItem("📤 GÉNÉRATION"): with gr.Row(): with gr.Column(scale=1): # Panneau de configuration gr.Markdown("### 🔐 Sécurité") key_in = gr.Textbox( label="Clé d'accès", placeholder="Entrez votre clé de déverrouillage...", type="password" ) gr.Markdown("### 📁 Document source") file_in = gr.File( label="Uploader votre document", file_types=[".pdf", ".docx", ".txt"] ) gr.Markdown("### 📋 Type de document") cat_in = gr.Dropdown( choices=[ "Article Scientifique", "Communication Scientifique", "Rapport d'Étude", "Rapport de Réunion", "Cours pour Étudiants", "Note de Synthèse", "Document de Travail" ], label="Sélectionner le type", value="Rapport d'Étude" ) with gr.Row(): preview_btn = gr.Button("👁️ Aperçu du plan", variant="secondary") generate_btn = gr.Button("✨ GÉNÉRER LA PRÉSENTATION", variant="primary", size="lg") with gr.Column(scale=1): # Zone de résultats gr.Markdown("### 📊 Résultats") file_out = gr.File(label="Télécharger PowerPoint") status_out = gr.Textbox( label="Statut", lines=4, interactive=False ) with gr.Accordion("📋 Aperçu du plan", open=False): preview_out = gr.Textbox( label="Structure de la présentation", lines=10, interactive=False ) # Gestionnaires d'événements generate_btn.click( process, inputs=[file_in, cat_in, key_in], outputs=[file_out, status_out] ) preview_btn.click( preview_outline, inputs=[file_in, cat_in, key_in], outputs=[preview_out] ) with gr.TabItem("📖 GUIDE D'UTILISATION"): gr.Markdown(""" ## Comment utiliser l'outil ? ### 1. Sélection du type de document - **Article Scientifique** : Pour publications académiques, thèses - **Communication Scientifique** : Présentations orales, posters - **Rapport d'Étude** : Études de marché, rapports techniques - **Rapport de Réunion** : Comptes-rendus, synthèses de réunion - **Cours pour Étudiants** : Supports pédagogiques - **Note de Synthèse** : Documents de synthèse - **Document de Travail** : Brouillons, versions de travail ### 2. Fonctionnalités intelligentes - ✓ Extraction automatique du titre - ✓ Identification des auteurs - ✓ Détection des sections clés - ✓ Adaptation du design (couleurs par type) - ✓ Structure de slides optimisée ### 3. Format de sortie - PowerPoint professionnel prêt à l'emploi - Design adapté au contexte - Contenu structuré logiquement - Aucune retouche nécessaire ### 4. Bonnes pratiques - Documents bien formatés = meilleurs résultats - Privilégier PDF ou DOCX - Vérifier le plan avant génération - Clé d'accès requise pour la sécurité """) with gr.TabItem("ℹ️ À PROPOS"): gr.Markdown(""" ## Lab_Math Premium Slide Generator ### Version 2.0 - Intelligence Adaptative **Développé par :** Division Prospective LAB_MATH & LABHP ### Caractéristiques techniques - 🤖 IA adaptative par type de document - 🎨 Design premium personnalisé - 🔒 Sécurité renforcée - 📊 Extraction intelligente des données - ⚡ Génération ultra-rapide ### Contact - 📧 Email: labmath.prospective@institution.edu - 🌐 Site: https://labmath-prospective.edu - 📍 Division Prospective - LAB_MATH © 2024 - Tous droits réservés """) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860)