import re import gradio as gr import pdfplumber import docx import tempfile from pptx import Presentation import traceback import os # =============================== # CONFIG SECURITE # =============================== ACCESS_KEY = "32015labmath@1a" # =============================== # TOKENIZER SIMPLE # =============================== def split_sentences(text): """Version robuste du tokenizer""" if not text or not isinstance(text, str): return [] # Nettoyage du texte text = re.sub(r'\s+', ' ', text.strip()) if not text: return [] # Split sur les ponctuations de fin de phrase sentences = re.split(r'(?<=[.!?])\s+(?=[A-Z0-9])', text) # Si pas assez de résultats, split alternatif if len(sentences) <= 2: sentences = re.split(r'[.!?]+', text) # Filtrage et nettoyage sentences = [s.strip() for s in sentences if s and len(s.strip()) > 15] return sentences if sentences else [text] # =============================== # ANALYSE DOCUMENT # =============================== class DocumentAnalyzer: def detect_document_type(self, text): """Détection améliorée du type de document""" if not text or not isinstance(text, str): return "Document Général" text = text.lower()[:5000] # Limiter pour la performance rules = { "Article Scientifique": [ "abstract", "résumé", "méthodologie", "methodology", "discussion", "résultats", "results", "bibliographie", "references", "introduction", "conclusion", "étude", "study" ], "Rapport de Réunion": [ "ordre du jour", "participants", "présents", "compte rendu", "décisions", "actions", "prochaine réunion", "points discutés", "réunion", "meeting", "discuté", "discussed" ], "Cours": [ "chapitre", "définition", "definition", "objectifs", "exemple", "example", "exercice", "exercise", "cours", "leçon", "lesson", "apprentissage", "learning" ], "Rapport Technique": [ "objectif", "objective", "analyse", "spécifications", "résultat", "result", "recommandation", "recommendation", "technique", "technical", "implémentation", "implementation" ] } scores = {doc_type: sum(text.count(word) for word in words) for doc_type, words in rules.items()} # Si tous les scores sont à 0 if max(scores.values()) == 0: return "Document Général" return max(scores, key=scores.get) def detect_sections(self, text): """Détection améliorée des sections""" if not text or not isinstance(text, str): return [text] if text else ["Contenu non disponible"] # Patterns de séparation de sections patterns = [ r'\n\s*[A-Z][A-Z\sÉÈÊÀÂÔÙÛÎÏÇ]{4,}\s*\n', # Titres en majuscules r'\n\s*\d+\.\s+[A-Z][^.]+?\n', # Titres numérotés r'\n\s*[A-Z][a-zéèêàôûïüç]+(?:\s+[a-zéèêàôûïüç]+)*:\s*\n', # Titres avec deux-points r'\n\s*#{1,3}\s+.+\n', # Markdown style r'\n\s*[IVX]+\.\s+[A-Z][^.\n]+\n' # Chiffres romains ] # Essayer chaque pattern sections = [] for pattern in patterns: split_text = re.split(pattern, f"\n{text}\n") if len(split_text) > 2: sections = split_text break # Si aucun pattern n'a fonctionné if not sections or len(sections) <= 1: # Découpage basé sur la longueur words = text.split() chunk_size = 500 sections = [' '.join(words[i:i+chunk_size]) for i in range(0, len(words), chunk_size)] # Nettoyage et filtrage sections = [s.strip() for s in sections if s and len(s.strip()) > 100] return sections if sections else [text[:1000]] # =============================== # RESUME # =============================== class ContentCompressor: def summarize(self, text, max_sentences=3): """Résumé amélioré avec scoring""" if not text or not isinstance(text, str): return ["Aucun contenu disponible"] sentences = split_sentences(text) if len(sentences) <= max_sentences: return sentences # Scoring des phrases scored_sentences = [] for i, sent in enumerate(sentences): # Score basé sur la position (favorise début et fin) position_score = 1.0 if i < len(sentences) * 0.2: # Début position_score = 1.5 elif i > len(sentences) * 0.8: # Fin position_score = 1.3 # Score basé sur la longueur length = len(sent) if 50 <= length <= 200: length_score = 1.5 elif length < 30: length_score = 0.5 else: length_score = 1.0 # Mots clés keywords = ["important", "conclusion", "résultat", "résumé", "significatif", "principal", "essentiel"] keyword_score = 1.0 + (0.2 * sum(1 for kw in keywords if kw in sent.lower())) total_score = position_score * length_score * keyword_score scored_sentences.append((sent, total_score)) # Sélection des meilleures phrases scored_sentences.sort(key=lambda x: x[1], reverse=True) best_sentences = [s[0] for s in scored_sentences[:max_sentences]] # Réorganisation selon l'ordre original best_sentences.sort(key=lambda x: text.find(x)) return best_sentences # =============================== # PLAN PRESENTATION # =============================== class AdaptivePlanner: def __init__(self): self.compressor = ContentCompressor() def build_plan(self, doc_type, sections): """Construction améliorée du plan""" # Structures par type de document structures = { "Article Scientifique": [ ("📋 Introduction", "Présentation du sujet et contexte"), ("🎯 Problématique", "Questions de recherche"), ("🔬 Méthodologie", "Approche et méthodes utilisées"), ("📊 Résultats", "Principaux résultats obtenus"), ("💭 Discussion", "Analyse et interprétation"), ("✅ Conclusion", "Synthèse et perspectives") ], "Rapport de Réunion": [ ("📅 Contexte", "Objectifs et cadre de la réunion"), ("👥 Participants", "Personnes présentes et absentes"), ("💬 Points discutés", "Sujets abordés"), ("⚡ Décisions", "Décisions prises"), ("📋 Actions", "Actions à réaliser"), ("📆 Prochaines étapes", "Planning et échéances") ], "Cours": [ ("🎯 Objectifs", "Ce que vous allez apprendre"), ("📚 Concepts clés", "Définitions et notions importantes"), ("🔍 Explications", "Détails et approfondissements"), ("💡 Exemples", "Cas pratiques et illustrations"), ("✍️ Exercices", "Mise en pratique"), ("📝 Résumé", "Points essentiels à retenir") ], "Rapport Technique": [ ("📌 Introduction", "Contexte et objectifs"), ("⚙️ Méthodologie", "Approche technique"), ("📈 Résultats", "Données et observations"), ("🔧 Analyse", "Interprétation technique"), ("💡 Recommandations", "Suggestions et améliorations"), ("✅ Conclusion", "Synthèse finale") ], "Document Général": [ ("📌 Introduction", "Présentation générale"), ("📋 Points principaux", "Idées clés"), ("📝 Développement", "Contenu détaillé"), ("💡 Synthèse", "Points importants"), ("✅ Conclusion", "Récapitulatif") ] } structure = structures.get(doc_type, structures["Document Général"]) slides = [] for i, (title, default_desc) in enumerate(structure): # Sélection du contenu approprié if i < len(sections) and sections[i]: content = self.compressor.summarize(sections[i], max_sentences=2) else: # Chercher une section appropriée found = False for section in sections: if len(section) > 100: content = self.compressor.summarize(section, max_sentences=2) found = True break if not found: content = [default_desc] slides.append({ "title": title, "content": content }) return slides # =============================== # GENERATION POWERPOINT # =============================== class SlideDesigner: def generate_presentation(self, title, slides): """Génération améliorée du PowerPoint""" try: prs = Presentation() # Page de titre slide_layout = prs.slide_layouts[0] slide = prs.slides.add_slide(slide_layout) slide.shapes.title.text = title if slide.placeholders[1].has_text_frame: slide.placeholders[1].text = "Généré automatiquement" # Slides de contenu content_layout = prs.slide_layouts[1] for slide_data in slides: slide = prs.slides.add_slide(content_layout) # Titre slide.shapes.title.text = slide_data["title"] # Contenu if slide.placeholders[1].has_text_frame: tf = slide.placeholders[1].text_frame tf.clear() for bullet in slide_data["content"]: if bullet and bullet.strip(): p = tf.add_paragraph() p.text = bullet[:150] + ("..." if len(bullet) > 150 else "") p.level = 0 # Sauvegarde with tempfile.NamedTemporaryFile(delete=False, suffix='.pptx') as tmp: ppt_path = tmp.name prs.save(ppt_path) return ppt_path except Exception as e: print(f"Erreur PowerPoint: {str(e)}") raise # =============================== # LECTURE FICHIERS # =============================== def read_file_safe(file): """Lecture sécurisée des fichiers""" if file is None: return None try: # Gestion des différents types d'entrée Gradio if hasattr(file, 'name'): file_path = file.name else: file_path = file if not os.path.exists(file_path): raise ValueError("Fichier temporaire non trouvé") # Lecture selon l'extension if file_path.endswith('.pdf'): text = "" with pdfplumber.open(file_path) as pdf: for page in pdf.pages[:10]: # Limiter à 10 pages page_text = page.extract_text() if page_text: text += page_text + "\n" return text.strip() or "Texte non extractible" elif file_path.endswith(('.docx', '.doc')): doc = docx.Document(file_path) text = [p.text for p in doc.paragraphs if p.text.strip()] return "\n".join(text) or "Document vide" else: # Texte encodings = ['utf-8', 'latin-1', 'cp1252'] for enc in encodings: try: with open(file_path, 'r', encoding=enc) as f: return f.read() except UnicodeDecodeError: continue return open(file_path, 'r', encoding='utf-8', errors='ignore').read() except Exception as e: raise ValueError(f"Erreur de lecture: {str(e)}") # =============================== # PIPELINE PRINCIPAL # =============================== analyzer = DocumentAnalyzer() planner = AdaptivePlanner() designer = SlideDesigner() def generate_presentation(file, access_key): """Fonction principale de génération""" try: # Vérification if access_key != ACCESS_KEY: return "Clé d'accès incorrecte", None if file is None: return "Veuillez télécharger un fichier", None # Lecture text = read_file_safe(file) if not text or len(text.strip()) < 50: return "Document trop court ou vide", None # Analyse doc_type = analyzer.detect_document_type(text) sections = analyzer.detect_sections(text) # Plan slides = planner.build_plan(doc_type, sections) # PowerPoint ppt_path = designer.generate_presentation(doc_type, slides) return f"✅ Présentation générée avec succès! ({doc_type})", ppt_path except Exception as e: print(traceback.format_exc()) return f"❌ Erreur: {str(e)}", None # =============================== # INTERFACE GRADIO # =============================== with gr.Blocks(title="SmartSlideAI", theme="soft") as interface: gr.Markdown(""" # 🤖 SmartSlideAI ### Transformez vos documents en présentations PowerPoint instantanément **Formats supportés:** PDF, DOCX, TXT """) with gr.Row(): with gr.Column(scale=1): file_input = gr.File( label="📄 Télécharger votre document", file_types=[".txt", ".pdf", ".docx", ".doc"], height=150 ) key_input = gr.Textbox( label="🔑 Clé d'accès", type="password", placeholder="Entrez votre clé" ) generate_btn = gr.Button("🚀 Générer la présentation", variant="primary") with gr.Column(scale=1): status_output = gr.Textbox( label="📊 Statut", interactive=False, lines=2 ) file_output = gr.File( label="📥 Télécharger la présentation", interactive=False ) generate_btn.click( fn=generate_presentation, inputs=[file_input, key_input], outputs=[status_output, file_output] ) gr.Markdown(""" --- ### 📝 Instructions 1. Téléchargez votre document (PDF, DOCX ou TXT) 2. Entrez votre clé d'accès 3. Cliquez sur "Générer" 4. Téléchargez votre présentation PowerPoint """) # Lancement if __name__ == "__main__": interface.launch( server_name="0.0.0.0", server_port=7860, share=False, debug=False )