Spaces:
Sleeping
Sleeping
Upload 5 files
Browse filesApplication de présentation intelligente des rapport d'études, des communications scientifiques, des comptes rendus, etc.
- ai_enhance.py +152 -0
- app.py +444 -0
- model_loader.py +29 -0
- presentation_generator.py +246 -0
- requirements.txt +13 -0
ai_enhance.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import string
|
| 3 |
+
from typing import Dict, List, Any
|
| 4 |
+
from collections import Counter
|
| 5 |
+
from model_loader import get_summarizer
|
| 6 |
+
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
class AIEnhance:
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.sentence_model = None
|
| 12 |
+
self.llm_service = None
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
logger.info("📦 Chargement du modèle de résumé IA (summarizer)...")
|
| 16 |
+
self.summarizer = get_summarizer()
|
| 17 |
+
logger.info("✅ Modèle summarizer chargé avec succès.")
|
| 18 |
+
except Exception as e:
|
| 19 |
+
logger.warning(f"❌ Échec du chargement du summarizer : {e}")
|
| 20 |
+
self.summarizer = None
|
| 21 |
+
|
| 22 |
+
logger.info("✅ AIEnhance initialisé.")
|
| 23 |
+
|
| 24 |
+
def smart_summarize(self, texte: str, max_length: int = 150) -> str:
|
| 25 |
+
"""Résumé intelligent du texte"""
|
| 26 |
+
try:
|
| 27 |
+
if self.summarizer and len(texte) > 200:
|
| 28 |
+
input_length = len(texte.split())
|
| 29 |
+
summary_max = min(max_length, input_length // 2)
|
| 30 |
+
summary_min = max(50, summary_max // 3)
|
| 31 |
+
|
| 32 |
+
summary = self.summarizer(
|
| 33 |
+
texte,
|
| 34 |
+
max_length=summary_max,
|
| 35 |
+
min_length=summary_min,
|
| 36 |
+
do_sample=False
|
| 37 |
+
)
|
| 38 |
+
return summary[0]['summary_text']
|
| 39 |
+
except Exception as e:
|
| 40 |
+
logger.warning(f"Résumé IA échoué, fallback: {e}")
|
| 41 |
+
|
| 42 |
+
return self._fallback_summary(texte)
|
| 43 |
+
|
| 44 |
+
def _fallback_summary(self, texte: str) -> str:
|
| 45 |
+
"""Résumé de fallback"""
|
| 46 |
+
sentences = [s.strip() for s in texte.split('.') if s.strip()]
|
| 47 |
+
if len(sentences) <= 2:
|
| 48 |
+
return texte
|
| 49 |
+
|
| 50 |
+
important_sentences = [sentences[0]]
|
| 51 |
+
keywords = self._extract_keywords(texte, 5)
|
| 52 |
+
for sentence in sentences[1:4]:
|
| 53 |
+
if any(keyword in sentence.lower() for keyword in keywords):
|
| 54 |
+
important_sentences.append(sentence)
|
| 55 |
+
|
| 56 |
+
return '. '.join(important_sentences[:3]) + '.'
|
| 57 |
+
|
| 58 |
+
def _extract_keywords(self, texte: str, top_n: int = 10) -> List[str]:
|
| 59 |
+
"""Extraction des mots-clés"""
|
| 60 |
+
try:
|
| 61 |
+
words = texte.translate(str.maketrans('', '', string.punctuation)).lower().split()
|
| 62 |
+
stop_words = {'le', 'la', 'les', 'de', 'des', 'du', 'et', 'est', 'son', 'ses', 'dans', 'pour', 'par'}
|
| 63 |
+
meaningful_words = [word for word in words if len(word) > 3 and word not in stop_words]
|
| 64 |
+
word_freq = Counter(meaningful_words)
|
| 65 |
+
return [word for word, count in word_freq.most_common(top_n)]
|
| 66 |
+
except Exception as e:
|
| 67 |
+
logger.warning(f"Erreur extraction keywords: {e}")
|
| 68 |
+
return ["analyse", "texte", "présentation", "contenu", "sujet"]
|
| 69 |
+
|
| 70 |
+
def _analyze_complexity(self, texte: str) -> float:
|
| 71 |
+
"""Analyse la complexité du texte"""
|
| 72 |
+
words = texte.split()
|
| 73 |
+
if not words:
|
| 74 |
+
return 0.0
|
| 75 |
+
avg_word_length = sum(len(word) for word in words) / len(words)
|
| 76 |
+
unique_words = len(set(words))
|
| 77 |
+
lexical_diversity = unique_words / len(words)
|
| 78 |
+
return min(1.0, (avg_word_length * 0.1 + lexical_diversity) / 2)
|
| 79 |
+
|
| 80 |
+
def _analyze_content_type(self, texte: str) -> str:
|
| 81 |
+
"""Analyse le type de contenu"""
|
| 82 |
+
texte_lower = texte.lower()
|
| 83 |
+
keywords_technique = {'technique', 'technologie', 'code', 'programmation', 'algorithme', 'données'}
|
| 84 |
+
keywords_commercial = {'vente', 'marketing', 'client', 'business', 'profit', 'stratégie'}
|
| 85 |
+
keywords_educatif = {'apprentissage', 'éducation', 'enseignement', 'cours', 'étudiant'}
|
| 86 |
+
|
| 87 |
+
if any(keyword in texte_lower for keyword in keywords_technique):
|
| 88 |
+
return "technique"
|
| 89 |
+
elif any(keyword in texte_lower for keyword in keywords_commercial):
|
| 90 |
+
return "commercial"
|
| 91 |
+
elif any(keyword in texte_lower for keyword in keywords_educatif):
|
| 92 |
+
return "éducatif"
|
| 93 |
+
else:
|
| 94 |
+
return "général"
|
| 95 |
+
|
| 96 |
+
# MÉTHODE CORRIGÉE - celle que vous appelez dans app.py
|
| 97 |
+
def _basic_analysis(self, texte: str) -> Dict[str, Any]:
|
| 98 |
+
"""Analyse de base du texte - MÉTHODE MANQUANTE AJOUTÉE"""
|
| 99 |
+
words = texte.split()
|
| 100 |
+
sentences = [s.strip() for s in texte.split('.') if s.strip()]
|
| 101 |
+
paragraphs = [p.strip() for p in texte.split('\n') if p.strip()]
|
| 102 |
+
|
| 103 |
+
# Créer des slides avec du contenu réel
|
| 104 |
+
slides = []
|
| 105 |
+
|
| 106 |
+
# Slide introduction
|
| 107 |
+
if sentences:
|
| 108 |
+
slides.append({
|
| 109 |
+
"title": "Introduction",
|
| 110 |
+
"content": sentences[0][:100] + "..." if len(sentences[0]) > 100 else sentences[0],
|
| 111 |
+
"type": "introduction"
|
| 112 |
+
})
|
| 113 |
+
|
| 114 |
+
# Slides de contenu (basées sur les paragraphes)
|
| 115 |
+
for i, para in enumerate(paragraphs[:4]):
|
| 116 |
+
if para and len(para) > 20:
|
| 117 |
+
slides.append({
|
| 118 |
+
"title": f"Point Clé {i+1}",
|
| 119 |
+
"content": para[:150] + "..." if len(para) > 150 else para,
|
| 120 |
+
"type": "content"
|
| 121 |
+
})
|
| 122 |
+
|
| 123 |
+
# Slide conclusion
|
| 124 |
+
if len(sentences) > 1:
|
| 125 |
+
slides.append({
|
| 126 |
+
"title": "Conclusion",
|
| 127 |
+
"content": "Synthèse des points principaux et perspectives futures",
|
| 128 |
+
"type": "conclusion"
|
| 129 |
+
})
|
| 130 |
+
|
| 131 |
+
return {
|
| 132 |
+
"statistics": {
|
| 133 |
+
"word_count": len(words),
|
| 134 |
+
"sentence_count": len(sentences),
|
| 135 |
+
"paragraph_count": len(paragraphs),
|
| 136 |
+
"avg_sentence_length": len(words) / len(sentences) if sentences else 0,
|
| 137 |
+
"complexity_score": self._analyze_complexity(texte)
|
| 138 |
+
},
|
| 139 |
+
"keywords": self._extract_keywords(texte, 8),
|
| 140 |
+
"recommended_structure": {
|
| 141 |
+
"total_slides": len(slides),
|
| 142 |
+
"slides": slides,
|
| 143 |
+
"recommended_style": "professionnel",
|
| 144 |
+
"estimated_duration": len(slides) * 2
|
| 145 |
+
},
|
| 146 |
+
"content_analysis": self._analyze_content_type(texte)
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
# Alias pour compatibilité
|
| 150 |
+
def analyze_text_advanced(self, texte: str) -> Dict[str, Any]:
|
| 151 |
+
"""Alias pour _basic_analysis pour la compatibilité"""
|
| 152 |
+
return self._basic_analysis(texte)
|
app.py
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import os
|
| 3 |
+
import tempfile
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
import logging
|
| 6 |
+
import json
|
| 7 |
+
import PyPDF2
|
| 8 |
+
from docx import Document
|
| 9 |
+
|
| 10 |
+
# Configuration logging
|
| 11 |
+
logging.basicConfig(level=logging.INFO)
|
| 12 |
+
logger = logging.getLogger(__name__)
|
| 13 |
+
|
| 14 |
+
# Initialisation des services
|
| 15 |
+
from ai_enhance import AIEnhance
|
| 16 |
+
from presentation_generator import PresentationGenerator
|
| 17 |
+
|
| 18 |
+
ai_service = AIEnhance()
|
| 19 |
+
presentation_generator = PresentationGenerator()
|
| 20 |
+
|
| 21 |
+
# Code secret
|
| 22 |
+
CODE_SECRET = "32015" # code à cinq chiffres
|
| 23 |
+
|
| 24 |
+
def verifier_code(code):
|
| 25 |
+
"""Vérifie si le code entré est correct"""
|
| 26 |
+
if code == CODE_SECRET:
|
| 27 |
+
return True, "🔓 Accès autorisé !"
|
| 28 |
+
else:
|
| 29 |
+
return False, "❌ Code incorrect. Veuillez réessayer."
|
| 30 |
+
|
| 31 |
+
# FONCTIONS D'EXTRACTION DE TEXTE
|
| 32 |
+
def extract_text_from_pdf(file_path):
|
| 33 |
+
"""Extrait le texte d'un fichier PDF"""
|
| 34 |
+
try:
|
| 35 |
+
with open(file_path, 'rb') as file:
|
| 36 |
+
pdf_reader = PyPDF2.PdfReader(file)
|
| 37 |
+
text = ""
|
| 38 |
+
for page in pdf_reader.pages:
|
| 39 |
+
text += page.extract_text() + "\n"
|
| 40 |
+
return text.strip()
|
| 41 |
+
except Exception as e:
|
| 42 |
+
logger.error(f"Erreur extraction PDF: {e}")
|
| 43 |
+
raise Exception(f"Erreur lors de l'extraction du PDF: {str(e)}")
|
| 44 |
+
|
| 45 |
+
def extract_text_from_docx(file_path):
|
| 46 |
+
"""Extrait le texte d'un fichier Word"""
|
| 47 |
+
try:
|
| 48 |
+
doc = Document(file_path)
|
| 49 |
+
text = ""
|
| 50 |
+
for paragraph in doc.paragraphs:
|
| 51 |
+
text += paragraph.text + "\n"
|
| 52 |
+
return text.strip()
|
| 53 |
+
except Exception as e:
|
| 54 |
+
logger.error(f"Erreur extraction DOCX: {e}")
|
| 55 |
+
raise Exception(f"Erreur lors de l'extraction du document Word: {str(e)}")
|
| 56 |
+
|
| 57 |
+
def extract_text_from_txt(file_path):
|
| 58 |
+
"""Extrait le texte d'un fichier texte"""
|
| 59 |
+
try:
|
| 60 |
+
with open(file_path, 'r', encoding='utf-8') as file:
|
| 61 |
+
return file.read().strip()
|
| 62 |
+
except Exception as e:
|
| 63 |
+
logger.error(f"Erreur extraction TXT: {e}")
|
| 64 |
+
raise Exception(f"Erreur lors de l'extraction du fichier texte: {str(e)}")
|
| 65 |
+
|
| 66 |
+
def extract_text_from_file(file_obj):
|
| 67 |
+
"""Extrait le texte selon le format du fichier"""
|
| 68 |
+
try:
|
| 69 |
+
file_path = file_obj.name
|
| 70 |
+
file_extension = os.path.splitext(file_path)[1].lower()
|
| 71 |
+
|
| 72 |
+
logger.info(f"📁 Extraction du fichier: {file_path}")
|
| 73 |
+
|
| 74 |
+
if file_extension == '.pdf':
|
| 75 |
+
return extract_text_from_pdf(file_path)
|
| 76 |
+
elif file_extension in ['.doc', '.docx']:
|
| 77 |
+
return extract_text_from_docx(file_path)
|
| 78 |
+
elif file_extension == '.txt':
|
| 79 |
+
return extract_text_from_txt(file_path)
|
| 80 |
+
else:
|
| 81 |
+
raise Exception(f"Format non supporté: {file_extension}")
|
| 82 |
+
|
| 83 |
+
except Exception as e:
|
| 84 |
+
logger.error(f"❌ Erreur extraction: {e}")
|
| 85 |
+
raise
|
| 86 |
+
|
| 87 |
+
def create_presentation_structure(texte):
|
| 88 |
+
"""Crée la structure de présentation avec analyse IA"""
|
| 89 |
+
try:
|
| 90 |
+
# ESSAYER DIFFÉRENTES MÉTHODES POUR TROUVER CELLE QUI FONCTIONNE
|
| 91 |
+
if hasattr(ai_service, '_basic_analysis'):
|
| 92 |
+
analysis = ai_service._basic_analysis(texte)
|
| 93 |
+
elif hasattr(ai_service, 'analyze_text_advanced'):
|
| 94 |
+
analysis = ai_service.analyze_text_advanced(texte)
|
| 95 |
+
else:
|
| 96 |
+
# Créer une analyse de base manuellement
|
| 97 |
+
analysis = {
|
| 98 |
+
"statistics": {
|
| 99 |
+
"word_count": len(texte.split()),
|
| 100 |
+
"sentence_count": texte.count('.'),
|
| 101 |
+
"paragraph_count": len([p for p in texte.split('\n\n') if p.strip()])
|
| 102 |
+
},
|
| 103 |
+
"keywords": ["texte", "analyse", "présentation"],
|
| 104 |
+
"recommended_structure": {
|
| 105 |
+
"slides": [
|
| 106 |
+
{"title": "Introduction", "content": "Présentation du sujet"},
|
| 107 |
+
{"title": "Développement", "content": "Points principaux"},
|
| 108 |
+
{"title": "Conclusion", "content": "Synthèse"}
|
| 109 |
+
],
|
| 110 |
+
"recommended_style": "professionnel"
|
| 111 |
+
},
|
| 112 |
+
"content_analysis": "général"
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
summary = ai_service.smart_summarize(texte)
|
| 116 |
+
structure = {
|
| 117 |
+
"title": f"Présentation: {analysis['content_analysis'].capitalize()}",
|
| 118 |
+
"slides": analysis["recommended_structure"]["slides"],
|
| 119 |
+
"key_points": analysis["keywords"][:8],
|
| 120 |
+
"style_recommendation": analysis["recommended_structure"]["recommended_style"],
|
| 121 |
+
"analysis_metadata": analysis
|
| 122 |
+
}
|
| 123 |
+
return structure, summary
|
| 124 |
+
|
| 125 |
+
except Exception as e:
|
| 126 |
+
logger.error(f"Erreur dans create_presentation_structure: {e}")
|
| 127 |
+
# Retourner une structure par défaut en cas d'erreur
|
| 128 |
+
default_structure = {
|
| 129 |
+
"title": "Présentation Générée par IA",
|
| 130 |
+
"slides": [
|
| 131 |
+
{"title": "Introduction", "content": "Votre texte a été analysé avec succès"},
|
| 132 |
+
{"title": "Points Principaux", "content": "Les insights clés ont été extraits"},
|
| 133 |
+
{"title": "Conclusion", "content": "Synthèse des éléments importants"}
|
| 134 |
+
],
|
| 135 |
+
"key_points": ["analyse", "texte", "présentation"],
|
| 136 |
+
"analysis_metadata": {"statistics": {"word_count": len(texte.split())}}
|
| 137 |
+
}
|
| 138 |
+
return default_structure, texte[:200] + "..."
|
| 139 |
+
|
| 140 |
+
def generate_presentation_gradio(texte, style="professionnel"):
|
| 141 |
+
"""Version Gradio améliorée avec prévisualisation"""
|
| 142 |
+
try:
|
| 143 |
+
if not texte or len(texte.strip()) < 50:
|
| 144 |
+
return None, None, "❌ Veuillez entrer au moins 50 caractères."
|
| 145 |
+
|
| 146 |
+
logger.info(f"🚀 Génération IA pour {len(texte)} caractères")
|
| 147 |
+
|
| 148 |
+
# Générer la structure
|
| 149 |
+
structure, summary = create_presentation_structure(texte)
|
| 150 |
+
filename = presentation_generator.generate_presentation(structure, style)
|
| 151 |
+
|
| 152 |
+
# Créer une belle prévisualisation
|
| 153 |
+
preview_html = create_preview_html(structure, summary)
|
| 154 |
+
|
| 155 |
+
logger.info("✅ Présentation générée avec succès!")
|
| 156 |
+
return filename, preview_html, f"🎉 Présentation générée! ({len(structure['slides'])} slides)"
|
| 157 |
+
|
| 158 |
+
except Exception as e:
|
| 159 |
+
logger.error(f"❌ Erreur lors de la génération: {e}")
|
| 160 |
+
return None, None, f"❌ Erreur: {str(e)}"
|
| 161 |
+
|
| 162 |
+
def create_preview_html(structure, summary):
|
| 163 |
+
"""Crée une belle prévisualisation HTML des slides"""
|
| 164 |
+
html_content = f"""
|
| 165 |
+
<div style="font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto;">
|
| 166 |
+
<h2 style="color: #2E86AB; border-bottom: 2px solid #2E86AB; padding-bottom: 10px;">
|
| 167 |
+
📊 Aperçu de votre Présentation
|
| 168 |
+
</h2>
|
| 169 |
+
|
| 170 |
+
<div style="background: #f8f9fa; padding: 15px; border-radius: 10px; margin-bottom: 20px;">
|
| 171 |
+
<h3 style="color: #2E86AB;">🎯 Titre Principal</h3>
|
| 172 |
+
<p style="font-size: 18px; font-weight: bold;">{structure['title']}</p>
|
| 173 |
+
</div>
|
| 174 |
+
|
| 175 |
+
<div style="background: #e8f4f8; padding: 15px; border-radius: 10px; margin-bottom: 20px;">
|
| 176 |
+
<h3 style="color: #2E86AB;">📝 Résumé IA</h3>
|
| 177 |
+
<p>{summary}</p>
|
| 178 |
+
</div>
|
| 179 |
+
|
| 180 |
+
<h3 style="color: #2E86AB;">🔄 Structure des Slides</h3>
|
| 181 |
+
"""
|
| 182 |
+
|
| 183 |
+
# Ajouter chaque slide
|
| 184 |
+
for i, slide in enumerate(structure['slides']):
|
| 185 |
+
html_content += f"""
|
| 186 |
+
<div style="background: white; border: 2px solid #2E86AB; border-radius: 10px; padding: 15px; margin: 10px 0;">
|
| 187 |
+
<div style="display: flex; align-items: center; margin-bottom: 10px;">
|
| 188 |
+
<span style="background: #2E86AB; color: white; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold;">
|
| 189 |
+
{i+1}
|
| 190 |
+
</span>
|
| 191 |
+
<h4 style="margin: 0 0 0 10px; color: #2E86AB;">{slide['title']}</h4>
|
| 192 |
+
</div>
|
| 193 |
+
<p style="margin: 0; color: #555;">{slide['content']}</p>
|
| 194 |
+
</div>
|
| 195 |
+
"""
|
| 196 |
+
|
| 197 |
+
# Ajouter les points clés
|
| 198 |
+
html_content += f"""
|
| 199 |
+
<div style="background: #fff3cd; padding: 15px; border-radius: 10px; margin-top: 20px;">
|
| 200 |
+
<h3 style="color: #856404;">🎯 Points Clés Identifiés</h3>
|
| 201 |
+
<div style="display: flex; flex-wrap: wrap; gap: 8px;">
|
| 202 |
+
"""
|
| 203 |
+
|
| 204 |
+
for point in structure['key_points']:
|
| 205 |
+
html_content += f'<span style="background: #856404; color: white; padding: 5px 10px; border-radius: 15px; font-size: 12px;">{point}</span>'
|
| 206 |
+
|
| 207 |
+
html_content += """
|
| 208 |
+
</div>
|
| 209 |
+
</div>
|
| 210 |
+
</div>
|
| 211 |
+
"""
|
| 212 |
+
|
| 213 |
+
return html_content
|
| 214 |
+
|
| 215 |
+
##### FONCTIONS UPLOAD AMÉLIORÉES #####
|
| 216 |
+
def handle_file_upload(files):
|
| 217 |
+
"""Gère l'upload de fichiers et extrait le texte"""
|
| 218 |
+
if not files:
|
| 219 |
+
return None, None, "❌ Aucun fichier sélectionné."
|
| 220 |
+
|
| 221 |
+
try:
|
| 222 |
+
file_obj = files[0] # Premier fichier
|
| 223 |
+
extracted_text = extract_text_from_file(file_obj)
|
| 224 |
+
|
| 225 |
+
if not extracted_text or len(extracted_text.strip()) < 10:
|
| 226 |
+
return None, None, "❌ Le fichier semble vide ou ne contient pas de texte extractible."
|
| 227 |
+
|
| 228 |
+
# Statistiques
|
| 229 |
+
word_count = len(extracted_text.split())
|
| 230 |
+
char_count = len(extracted_text)
|
| 231 |
+
|
| 232 |
+
message = f"✅ Fichier traité avec succès! 📊 {word_count} mots, {char_count} caractères"
|
| 233 |
+
|
| 234 |
+
return extracted_text, extracted_text, message
|
| 235 |
+
|
| 236 |
+
except Exception as e:
|
| 237 |
+
logger.error(f"Erreur upload: {e}")
|
| 238 |
+
return None, None, f"❌ Erreur: {str(e)}"
|
| 239 |
+
|
| 240 |
+
def generate_from_uploaded_text(analyzed_text, style="professionnel"):
|
| 241 |
+
"""Génère la présentation à partir du texte uploadé"""
|
| 242 |
+
return generate_presentation_gradio(analyzed_text, style)
|
| 243 |
+
|
| 244 |
+
def analyze_text_gradio(texte):
|
| 245 |
+
"""Version Gradio de votre api_analyze()"""
|
| 246 |
+
try:
|
| 247 |
+
if not texte or len(texte.strip()) < 10:
|
| 248 |
+
return "❌ Texte trop court. Minimum 10 caractères."
|
| 249 |
+
|
| 250 |
+
structure, summary = create_presentation_structure(texte)
|
| 251 |
+
|
| 252 |
+
# Formatage pour l'interface Gradio
|
| 253 |
+
result = f"""
|
| 254 |
+
## 📊 Analyse IA du Texte
|
| 255 |
+
|
| 256 |
+
**Statistiques:**
|
| 257 |
+
- {structure['analysis_metadata']['statistics']['word_count']} mots
|
| 258 |
+
- {structure['analysis_metadata']['statistics']['sentence_count']} phrases
|
| 259 |
+
- {structure['analysis_metadata']['statistics']['paragraph_count']} paragraphes
|
| 260 |
+
|
| 261 |
+
**🎯 Thèmes identifiés:**
|
| 262 |
+
{', '.join(structure['key_points'][:8])}
|
| 263 |
+
|
| 264 |
+
**📝 Résumé:**
|
| 265 |
+
{summary}
|
| 266 |
+
|
| 267 |
+
**🏗️ Structure proposée:**
|
| 268 |
+
"""
|
| 269 |
+
for i, slide in enumerate(structure['slides']):
|
| 270 |
+
result += f"\n{i+1}. **{slide['title']}** - {slide['content'][:100]}..."
|
| 271 |
+
|
| 272 |
+
return result
|
| 273 |
+
|
| 274 |
+
except Exception as e:
|
| 275 |
+
return f"❌ Erreur d'analyse: {str(e)}"
|
| 276 |
+
|
| 277 |
+
# INTERFACE PRINCIPALE AVEC AUTHENTIFICATION
|
| 278 |
+
with gr.Blocks(theme=gr.themes.Soft(), title="Générateur de Présentation IA") as demo:
|
| 279 |
+
|
| 280 |
+
# Écran d'authentification
|
| 281 |
+
with gr.Column(visible=True) as auth_screen:
|
| 282 |
+
gr.Markdown("# 🔒 Accès Sécurisé")
|
| 283 |
+
gr.Markdown("Veuillez entrer le code d'accès à 5 chiffres")
|
| 284 |
+
|
| 285 |
+
with gr.Row():
|
| 286 |
+
code_input = gr.Textbox(
|
| 287 |
+
label="Code d'accès",
|
| 288 |
+
type="text",
|
| 289 |
+
max_lines=1,
|
| 290 |
+
placeholder="Entrez les 5 chiffres...",
|
| 291 |
+
elem_id="code_input"
|
| 292 |
+
)
|
| 293 |
+
verifier_btn = gr.Button("Vérifier", variant="primary")
|
| 294 |
+
|
| 295 |
+
message_sortie = gr.Textbox(label="Statut", interactive=False)
|
| 296 |
+
|
| 297 |
+
# Application principale (cachée au début)
|
| 298 |
+
with gr.Column(visible=False) as app_screen:
|
| 299 |
+
gr.Markdown("""
|
| 300 |
+
# 🧠 Générateur de Présentation IA Intelligente
|
| 301 |
+
**Powered by Lab_Math_and Labhp & CIE Label_Bertoua**
|
| 302 |
+
|
| 303 |
+
Transformez votre texte en présentation PowerPoint professionnelle en quelques secondes !
|
| 304 |
+
""")
|
| 305 |
+
|
| 306 |
+
with gr.Tab("🚀 Générer à partir de Texte"):
|
| 307 |
+
with gr.Row():
|
| 308 |
+
with gr.Column():
|
| 309 |
+
text_input = gr.Textbox(
|
| 310 |
+
label="📝 Collez votre texte ici",
|
| 311 |
+
placeholder="Collez ou tapez votre texte, article, rapport... (minimum 50 caractères)",
|
| 312 |
+
lines=12,
|
| 313 |
+
max_lines=20
|
| 314 |
+
)
|
| 315 |
+
style_dropdown = gr.Dropdown(
|
| 316 |
+
choices=["professionnel", "moderne", "creatif"],
|
| 317 |
+
label="🎨 Style de présentation",
|
| 318 |
+
value="professionnel",
|
| 319 |
+
info="Choisissez le style visuel de votre présentation"
|
| 320 |
+
)
|
| 321 |
+
generate_btn = gr.Button("🚀 Générer la Présentation", variant="primary", size="lg")
|
| 322 |
+
|
| 323 |
+
with gr.Column():
|
| 324 |
+
output_file = gr.File(label="📥 Télécharger la Présentation", file_types=[".pptx"])
|
| 325 |
+
preview_output = gr.HTML(label="👁️ Aperçu de la Structure")
|
| 326 |
+
output_message = gr.Textbox(label="📋 Statut", interactive=False)
|
| 327 |
+
|
| 328 |
+
generate_btn.click(
|
| 329 |
+
fn=generate_presentation_gradio,
|
| 330 |
+
inputs=[text_input, style_dropdown],
|
| 331 |
+
outputs=[output_file, preview_output, output_message]
|
| 332 |
+
)
|
| 333 |
+
|
| 334 |
+
with gr.Tab("📁 Générer à partir de Fichier"):
|
| 335 |
+
gr.Markdown("### 📤 Uploader votre document")
|
| 336 |
+
gr.Markdown("""
|
| 337 |
+
**Formats supportés:**
|
| 338 |
+
- 📄 PDF (rapports, articles)
|
| 339 |
+
- 📝 DOC/DOCX (documents Word)
|
| 340 |
+
- 📋 TXT (fichiers texte)
|
| 341 |
+
""")
|
| 342 |
+
|
| 343 |
+
with gr.Row():
|
| 344 |
+
with gr.Column():
|
| 345 |
+
upload_button = gr.UploadButton(
|
| 346 |
+
"📁 Choisir un fichier",
|
| 347 |
+
file_types=[".pdf", ".doc", ".docx", ".txt"],
|
| 348 |
+
file_count="single",
|
| 349 |
+
size="lg"
|
| 350 |
+
)
|
| 351 |
+
uploaded_file_info = gr.Textbox(label="📋 Fichier sélectionné", interactive=False)
|
| 352 |
+
extract_btn = gr.Button("🔍 Extraire et Analyser", variant="primary")
|
| 353 |
+
|
| 354 |
+
with gr.Column():
|
| 355 |
+
extracted_text = gr.Textbox(
|
| 356 |
+
label="📝 Texte extrait",
|
| 357 |
+
interactive=False,
|
| 358 |
+
lines=8,
|
| 359 |
+
max_lines=12
|
| 360 |
+
)
|
| 361 |
+
upload_status = gr.Textbox(label="📊 Statut extraction", interactive=False)
|
| 362 |
+
|
| 363 |
+
with gr.Row():
|
| 364 |
+
file_style_dropdown = gr.Dropdown(
|
| 365 |
+
choices=["professionnel", "moderne", "creatif"],
|
| 366 |
+
label="🎨 Style de présentation",
|
| 367 |
+
value="professionnel"
|
| 368 |
+
)
|
| 369 |
+
generate_from_file_btn = gr.Button("🚀 Générer la Présentation", variant="primary")
|
| 370 |
+
|
| 371 |
+
with gr.Row():
|
| 372 |
+
file_output = gr.File(label="📥 Présentation Générée", file_types=[".pptx"])
|
| 373 |
+
file_preview = gr.HTML(label="👁️ Aperçu")
|
| 374 |
+
file_message = gr.Textbox(label="📋 Statut génération", interactive=False)
|
| 375 |
+
|
| 376 |
+
with gr.Tab("🔍 Analyser le Texte"):
|
| 377 |
+
with gr.Row():
|
| 378 |
+
with gr.Column():
|
| 379 |
+
analyze_text_input = gr.Textbox(
|
| 380 |
+
label="📝 Texte à analyser",
|
| 381 |
+
placeholder="Collez votre texte pour l'analyse IA...",
|
| 382 |
+
lines=8
|
| 383 |
+
)
|
| 384 |
+
analyze_btn = gr.Button("🔍 Analyser avec IA", variant="secondary")
|
| 385 |
+
|
| 386 |
+
with gr.Column():
|
| 387 |
+
analysis_output = gr.Markdown(label="📊 Résultats de l'analyse")
|
| 388 |
+
|
| 389 |
+
analyze_btn.click(
|
| 390 |
+
fn=analyze_text_gradio,
|
| 391 |
+
inputs=[analyze_text_input],
|
| 392 |
+
outputs=[analysis_output]
|
| 393 |
+
)
|
| 394 |
+
|
| 395 |
+
# GESTION DES INTERACTIONS UPLOAD
|
| 396 |
+
def update_file_info(files):
|
| 397 |
+
"""Met à jour les informations du fichier uploadé"""
|
| 398 |
+
if files:
|
| 399 |
+
file_obj = files[0]
|
| 400 |
+
file_size = os.path.getsize(file_obj.name) / 1024 # KB
|
| 401 |
+
return f"📄 {os.path.basename(file_obj.name)} ({file_size:.1f} KB)"
|
| 402 |
+
return "Aucun fichier sélectionné"
|
| 403 |
+
|
| 404 |
+
# Lier les événements upload
|
| 405 |
+
upload_button.upload(
|
| 406 |
+
fn=update_file_info,
|
| 407 |
+
inputs=[upload_button],
|
| 408 |
+
outputs=[uploaded_file_info]
|
| 409 |
+
)
|
| 410 |
+
|
| 411 |
+
extract_btn.click(
|
| 412 |
+
fn=handle_file_upload,
|
| 413 |
+
inputs=[upload_button],
|
| 414 |
+
outputs=[extracted_text, extracted_text, upload_status]
|
| 415 |
+
)
|
| 416 |
+
|
| 417 |
+
generate_from_file_btn.click(
|
| 418 |
+
fn=generate_from_uploaded_text,
|
| 419 |
+
inputs=[extracted_text, file_style_dropdown],
|
| 420 |
+
outputs=[file_output, file_preview, file_message]
|
| 421 |
+
)
|
| 422 |
+
|
| 423 |
+
def gerer_acces(code):
|
| 424 |
+
"""Gère l'authentification et affiche l'application si le code est correct"""
|
| 425 |
+
est_valide, message = verifier_code(code)
|
| 426 |
+
return (
|
| 427 |
+
message,
|
| 428 |
+
gr.update(visible=not est_valide), # Cacher l'écran d'auth
|
| 429 |
+
gr.update(visible=est_valide) # Afficher l'application
|
| 430 |
+
)
|
| 431 |
+
|
| 432 |
+
# Lier le bouton de vérification
|
| 433 |
+
verifier_btn.click(
|
| 434 |
+
fn=gerer_acces,
|
| 435 |
+
inputs=[code_input],
|
| 436 |
+
outputs=[message_sortie, auth_screen, app_screen]
|
| 437 |
+
)
|
| 438 |
+
|
| 439 |
+
if __name__ == "__main__":
|
| 440 |
+
demo.launch(
|
| 441 |
+
server_name="0.0.0.0",
|
| 442 |
+
server_port=7860,
|
| 443 |
+
share=False
|
| 444 |
+
)
|
model_loader.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from transformers import pipeline
|
| 2 |
+
|
| 3 |
+
# Liste des modèles de summarization plus légers
|
| 4 |
+
MODEL_OPTIONS = [
|
| 5 |
+
"Falconsai/text_summarization", # Original
|
| 6 |
+
"facebook/bart-large-cnn", # Alternative 1
|
| 7 |
+
"t5-small", # Alternative 2 (léger)
|
| 8 |
+
"mrm8488/bert-mini-finetuned-cnn_daily_mail-summarization" # Alternative 3
|
| 9 |
+
]
|
| 10 |
+
|
| 11 |
+
summarizer = None # Global
|
| 12 |
+
|
| 13 |
+
def load_model_with_fallback():
|
| 14 |
+
for model_name in MODEL_OPTIONS:
|
| 15 |
+
try:
|
| 16 |
+
print(f"Tentative de chargement: {model_name}")
|
| 17 |
+
model = pipeline("summarization", model=model_name)
|
| 18 |
+
print(f"Succès avec: {model_name}")
|
| 19 |
+
return model
|
| 20 |
+
except Exception as e:
|
| 21 |
+
print(f"Échec avec {model_name}: {e}")
|
| 22 |
+
continue
|
| 23 |
+
raise Exception("Aucun modèle n'a pu être chargé")
|
| 24 |
+
|
| 25 |
+
def get_summarizer():
|
| 26 |
+
global summarizer
|
| 27 |
+
if summarizer is None:
|
| 28 |
+
summarizer = load_model_with_fallback()
|
| 29 |
+
return summarizer
|
presentation_generator.py
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pptx import Presentation
|
| 2 |
+
from pptx.util import Inches, Pt
|
| 3 |
+
from pptx.enum.text import PP_ALIGN
|
| 4 |
+
from pptx.dml.color import RGBColor
|
| 5 |
+
import tempfile
|
| 6 |
+
import logging
|
| 7 |
+
import re
|
| 8 |
+
|
| 9 |
+
logger = logging.getLogger(__name__)
|
| 10 |
+
|
| 11 |
+
class PresentationGenerator:
|
| 12 |
+
def __init__(self):
|
| 13 |
+
self.styles = {
|
| 14 |
+
"professionnel": {
|
| 15 |
+
"title_color": RGBColor(0, 0, 128),
|
| 16 |
+
"text_color": RGBColor(0, 0, 0),
|
| 17 |
+
"background_color": RGBColor(255, 255, 255),
|
| 18 |
+
"font_name": "Calibri"
|
| 19 |
+
},
|
| 20 |
+
"moderne": {
|
| 21 |
+
"title_color": RGBColor(220, 20, 60),
|
| 22 |
+
"text_color": RGBColor(50, 50, 50),
|
| 23 |
+
"background_color": RGBColor(240, 240, 240),
|
| 24 |
+
"font_name": "Segoe UI"
|
| 25 |
+
},
|
| 26 |
+
"creatif": {
|
| 27 |
+
"title_color": RGBColor(75, 0, 130),
|
| 28 |
+
"text_color": RGBColor(0, 0, 0),
|
| 29 |
+
"background_color": RGBColor(255, 250, 240),
|
| 30 |
+
"font_name": "Arial"
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
def generate_presentation(self, structure, style="professionnel"):
|
| 35 |
+
"""Génère une présentation PowerPoint à partir de la structure IA"""
|
| 36 |
+
try:
|
| 37 |
+
logger.info("📄 Initialisation de la présentation...")
|
| 38 |
+
prs = Presentation()
|
| 39 |
+
style_config = self.styles.get(style, self.styles["professionnel"])
|
| 40 |
+
|
| 41 |
+
logger.info("🎯 Ajout de la slide de titre...")
|
| 42 |
+
self._add_title_slide(prs, structure, style_config)
|
| 43 |
+
|
| 44 |
+
logger.info("📄 Ajout des slides de contenu enrichies...")
|
| 45 |
+
for i, slide_data in enumerate(structure.get('slides', [])):
|
| 46 |
+
logger.info(f" ➕ Slide {i+1}: {slide_data.get('title', 'Sans titre')}")
|
| 47 |
+
self._add_enhanced_content_slide(prs, slide_data, style_config, i)
|
| 48 |
+
|
| 49 |
+
if structure.get('key_points'):
|
| 50 |
+
logger.info("🧠 Ajout des points clés enrichis...")
|
| 51 |
+
self._add_enhanced_keypoints_slide(prs, structure, style_config)
|
| 52 |
+
|
| 53 |
+
logger.info("📈 Ajout de la slide statistiques...")
|
| 54 |
+
self._add_statistics_slide(prs, structure, style_config)
|
| 55 |
+
|
| 56 |
+
logger.info("🏁 Ajout de la conclusion enrichie...")
|
| 57 |
+
self._add_enhanced_conclusion_slide(prs, structure, style_config)
|
| 58 |
+
|
| 59 |
+
logger.info("💾 Sauvegarde de la présentation...")
|
| 60 |
+
temp_file = tempfile.NamedTemporaryFile(delete=False, suffix='.pptx')
|
| 61 |
+
prs.save(temp_file.name)
|
| 62 |
+
|
| 63 |
+
logger.info(f"✅ Présentation enregistrée dans : {temp_file.name}")
|
| 64 |
+
return temp_file.name
|
| 65 |
+
|
| 66 |
+
except Exception as e:
|
| 67 |
+
logger.error(f"❌ Erreur génération présentation: {e}")
|
| 68 |
+
raise
|
| 69 |
+
|
| 70 |
+
def _add_title_slide(self, prs, structure, style_config):
|
| 71 |
+
"""Ajoute la slide de titre améliorée"""
|
| 72 |
+
slide_layout = prs.slide_layouts[0]
|
| 73 |
+
slide = prs.slides.add_slide(slide_layout)
|
| 74 |
+
title = slide.shapes.title
|
| 75 |
+
subtitle = slide.placeholders[1]
|
| 76 |
+
|
| 77 |
+
title.text = structure.get('title', 'Présentation Générée par IA')
|
| 78 |
+
subtitle.text = f"Style: {style_config['font_name']}\nGénéré intelligemment par IA\n{len(structure.get('slides', []))} slides"
|
| 79 |
+
|
| 80 |
+
def _add_enhanced_content_slide(self, prs, slide_data, style_config, slide_index):
|
| 81 |
+
"""Ajoute une slide de contenu enrichie"""
|
| 82 |
+
slide_layout = prs.slide_layouts[1]
|
| 83 |
+
slide = prs.slides.add_slide(slide_layout)
|
| 84 |
+
|
| 85 |
+
title_shape = slide.shapes.title
|
| 86 |
+
content_shape = slide.placeholders[1] if len(slide.placeholders) > 1 else self._create_textbox(slide)
|
| 87 |
+
|
| 88 |
+
# Titre amélioré
|
| 89 |
+
title_shape.text = self._enhance_slide_title(slide_data.get('title', f'Slide {slide_index + 1}'), slide_index)
|
| 90 |
+
|
| 91 |
+
# Contenu enrichi
|
| 92 |
+
enhanced_content = self._enhance_slide_content(slide_data.get('content', ''), slide_index)
|
| 93 |
+
content_shape.text = enhanced_content
|
| 94 |
+
|
| 95 |
+
self._apply_style(title_shape, style_config, is_title=True)
|
| 96 |
+
self._apply_style(content_shape, style_config, is_title=False)
|
| 97 |
+
|
| 98 |
+
def _enhance_slide_title(self, title, slide_index):
|
| 99 |
+
"""Améliore les titres des slides"""
|
| 100 |
+
title_enhancements = {
|
| 101 |
+
0: ["🚀 Introduction", "📋 Aperçu Général", "🎯 Présentation du Sujet"],
|
| 102 |
+
1: ["🔍 Analyse Détaillée", "📊 Points Clés", "💡 Insights Principaux"],
|
| 103 |
+
2: ["🏗️ Développement", "📈 Données et Faits", "🔬 Analyse Approfondie"],
|
| 104 |
+
3: ["💎 Synthèse", "🎖️ Points Forts", "📝 Résumé Exécutif"]
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
base_title = title_enhancements.get(slide_index, ["📄 Slide", "💼 Contenu", "📖 Section"])
|
| 108 |
+
return f"{base_title[0]} : {title}"
|
| 109 |
+
|
| 110 |
+
def _enhance_slide_content(self, content, slide_index):
|
| 111 |
+
"""Enrichit le contenu des slides"""
|
| 112 |
+
if not content or len(content.strip()) < 10:
|
| 113 |
+
default_contents = [
|
| 114 |
+
"Présentation du contexte et des objectifs\n• Introduction au sujet principal\n• Définition des enjeux clés\n• Présentation de la structure",
|
| 115 |
+
"Analyse des points essentiels\n• Données chiffrées et statistiques\n• Tendances observées\n• Perspectives d'évolution",
|
| 116 |
+
"Développement des concepts clés\n• Études de cas concrètes\n• Recommandations pratiques\n• Implications stratégiques",
|
| 117 |
+
"Synthèse des éléments majeurs\n• Points à retenir\n• Actions recommandées\n• Prochaines étapes"
|
| 118 |
+
]
|
| 119 |
+
return default_contents[slide_index % len(default_contents)]
|
| 120 |
+
|
| 121 |
+
# Enrichir le contenu existant
|
| 122 |
+
enhancements = [
|
| 123 |
+
"\n\n🎯 Points clés à retenir :\n• Information principale\n• Donnée significative\n• Insight important",
|
| 124 |
+
"\n\n📊 Éléments marquants :\n• Chiffre clé\n• Tendance observée\n• Recommandation",
|
| 125 |
+
"\n\n💡 Actions recommandées :\n• Mesure concrète\n• Stratégie à adopter\n• Perspective future",
|
| 126 |
+
"\n\n🌟 Impact et valeur :\n• Bénéfice attendu\n• Résultat potentiel\n• Contribution majeure"
|
| 127 |
+
]
|
| 128 |
+
|
| 129 |
+
enhanced_content = content
|
| 130 |
+
if len(content) > 100:
|
| 131 |
+
# Ajouter des puces si le contenu est long
|
| 132 |
+
sentences = [s.strip() for s in content.split('.') if s.strip()]
|
| 133 |
+
if len(sentences) > 2:
|
| 134 |
+
enhanced_content = "• " + "\n• ".join(sentences[:4])
|
| 135 |
+
|
| 136 |
+
return enhanced_content + enhancements[slide_index % len(enhancements)]
|
| 137 |
+
|
| 138 |
+
def _add_enhanced_keypoints_slide(self, prs, structure, style_config):
|
| 139 |
+
"""Ajoute la slide des points clés enrichie"""
|
| 140 |
+
slide_layout = prs.slide_layouts[1]
|
| 141 |
+
slide = prs.slides.add_slide(slide_layout)
|
| 142 |
+
|
| 143 |
+
title_shape = slide.shapes.title
|
| 144 |
+
content_shape = slide.placeholders[1] if len(slide.placeholders) > 1 else self._create_textbox(slide)
|
| 145 |
+
|
| 146 |
+
title_shape.text = "🎯 Points Clés Identifiés par IA"
|
| 147 |
+
|
| 148 |
+
# Points clés enrichis
|
| 149 |
+
key_points = structure.get('key_points', [])
|
| 150 |
+
enhanced_points = []
|
| 151 |
+
|
| 152 |
+
for i, point in enumerate(key_points[:10]): # Limiter à 10 points max
|
| 153 |
+
icons = ["🔸", "🌟", "💡", "📌", "🎯", "⚡", "✅", "📊", "🔍", "🏆"]
|
| 154 |
+
enhanced_points.append(f"{icons[i % len(icons)]} {point}")
|
| 155 |
+
|
| 156 |
+
content_text = "Principaux insights extraits de l'analyse :\n\n" + "\n".join(enhanced_points)
|
| 157 |
+
|
| 158 |
+
# Ajouter une conclusion sur les points clés
|
| 159 |
+
content_text += f"\n\n📈 Synthèse :\n• {len(key_points)} thèmes majeurs identifiés\n• Analyse sémantique avancée\n• Priorisation intelligente"
|
| 160 |
+
|
| 161 |
+
content_shape.text = content_text
|
| 162 |
+
|
| 163 |
+
self._apply_style(title_shape, style_config, is_title=True)
|
| 164 |
+
self._apply_style(content_shape, style_config, is_title=False)
|
| 165 |
+
|
| 166 |
+
def _add_statistics_slide(self, prs, structure, style_config):
|
| 167 |
+
"""Ajoute une slide avec les statistiques d'analyse"""
|
| 168 |
+
slide_layout = prs.slide_layouts[1]
|
| 169 |
+
slide = prs.slides.add_slide(slide_layout)
|
| 170 |
+
|
| 171 |
+
title_shape = slide.shapes.title
|
| 172 |
+
content_shape = slide.placeholders[1] if len(slide.placeholders) > 1 else self._create_textbox(slide)
|
| 173 |
+
|
| 174 |
+
title_shape.text = "📊 Métriques d'Analyse"
|
| 175 |
+
|
| 176 |
+
stats = structure.get('analysis_metadata', {}).get('statistics', {})
|
| 177 |
+
content_text = "Analyse quantitative du contenu :\n\n"
|
| 178 |
+
|
| 179 |
+
metrics = [
|
| 180 |
+
f"📝 {stats.get('word_count', 0)} mots analysés",
|
| 181 |
+
f"💬 {stats.get('sentence_count', 0)} phrases traitées",
|
| 182 |
+
f"📄 {stats.get('paragraph_count', 0)} paragraphes examinés",
|
| 183 |
+
f"🎯 {len(structure.get('key_points', []))} thèmes clés identifiés",
|
| 184 |
+
f"🔄 {len(structure.get('slides', []))} slides générées",
|
| 185 |
+
f"⚡ Complexité : {stats.get('complexity_score', 0.5):.1f}/1.0"
|
| 186 |
+
]
|
| 187 |
+
|
| 188 |
+
content_text += "\n".join(metrics)
|
| 189 |
+
content_text += "\n\n🔍 Méthodologie :\n• Analyse sémantique IA\n• Extraction de concepts clés\n• Structuration intelligente"
|
| 190 |
+
|
| 191 |
+
content_shape.text = content_text
|
| 192 |
+
|
| 193 |
+
self._apply_style(title_shape, style_config, is_title=True)
|
| 194 |
+
self._apply_style(content_shape, style_config, is_title=False)
|
| 195 |
+
|
| 196 |
+
def _add_enhanced_conclusion_slide(self, prs, structure, style_config):
|
| 197 |
+
"""Ajoute la slide de conclusion enrichie"""
|
| 198 |
+
slide_layout = prs.slide_layouts[1]
|
| 199 |
+
slide = prs.slides.add_slide(slide_layout)
|
| 200 |
+
|
| 201 |
+
title_shape = slide.shapes.title
|
| 202 |
+
content_shape = slide.placeholders[1] if len(slide.placeholders) > 1 else self._create_textbox(slide)
|
| 203 |
+
|
| 204 |
+
title_shape.text = "🏁 Conclusion & Perspectives"
|
| 205 |
+
|
| 206 |
+
conclusion_text = "Synthèse de la présentation :\n\n"
|
| 207 |
+
conclusion_text += "✅ Analyse complète réalisée avec succès\n"
|
| 208 |
+
conclusion_text += "🎯 Points essentiels mis en lumière\n"
|
| 209 |
+
conclusion_text += "💡 Insights actionnables identifiés\n\n"
|
| 210 |
+
|
| 211 |
+
conclusion_text += "Prochaines étapes recommandées :\n"
|
| 212 |
+
conclusion_text += "• Approfondir les points clés\n"
|
| 213 |
+
conclusion_text += "• Mettre en œuvre les recommandations\n"
|
| 214 |
+
conclusion_text += "• Mesurer l'impact des actions\n\n"
|
| 215 |
+
|
| 216 |
+
conclusion_text += "🧠 Présentation générée automatiquement\n"
|
| 217 |
+
conclusion_text += "avec technologie IA avancée\n"
|
| 218 |
+
conclusion_text += "Lab_Math & Labhp - CIE Label_Bertoua"
|
| 219 |
+
|
| 220 |
+
content_shape.text = conclusion_text
|
| 221 |
+
|
| 222 |
+
self._apply_style(title_shape, style_config, is_title=True)
|
| 223 |
+
self._apply_style(content_shape, style_config, is_title=False)
|
| 224 |
+
|
| 225 |
+
def _create_textbox(self, slide):
|
| 226 |
+
"""Crée une textbox manuelle si le placeholder n'existe pas"""
|
| 227 |
+
return slide.shapes.add_textbox(Inches(1), Inches(1.5), Inches(8), Inches(5))
|
| 228 |
+
|
| 229 |
+
def _apply_style(self, shape, style_config, is_title=True):
|
| 230 |
+
"""Applique le style aux éléments textuels"""
|
| 231 |
+
if not hasattr(shape, "text_frame") or shape.text_frame is None:
|
| 232 |
+
logger.warning("⚠️ Shape sans text_frame, style non appliqué.")
|
| 233 |
+
return
|
| 234 |
+
|
| 235 |
+
try:
|
| 236 |
+
for paragraph in shape.text_frame.paragraphs:
|
| 237 |
+
for run in paragraph.runs:
|
| 238 |
+
if run.font:
|
| 239 |
+
run.font.name = style_config["font_name"]
|
| 240 |
+
run.font.color.rgb = (
|
| 241 |
+
style_config["title_color"] if is_title else style_config["text_color"]
|
| 242 |
+
)
|
| 243 |
+
run.font.size = Pt(32 if is_title else 16)
|
| 244 |
+
run.font.bold = is_title
|
| 245 |
+
except Exception as e:
|
| 246 |
+
logger.warning(f"❌ Échec de l'application du style: {e}")
|
requirements.txt
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0.0
|
| 2 |
+
python-pptx>=1.0.0
|
| 3 |
+
PyPDF2>=3.0.0
|
| 4 |
+
python-docx>=1.1.0
|
| 5 |
+
transformers>=4.30.0
|
| 6 |
+
torch>=2.0.0
|
| 7 |
+
flask>=2.3.0
|
| 8 |
+
numpy==1.24.3
|
| 9 |
+
scikit-learn==1.2.2
|
| 10 |
+
sentence-transformers==2.2.2
|
| 11 |
+
nltk==3.8.1
|
| 12 |
+
requests==2.28.2
|
| 13 |
+
python-dotenv==1.0.0
|