sohfossted commited on
Commit
cfc4fb9
·
verified ·
1 Parent(s): db86315

Upload 5 files

Browse files

Application de présentation intelligente des rapport d'études, des communications scientifiques, des comptes rendus, etc.

Files changed (5) hide show
  1. ai_enhance.py +152 -0
  2. app.py +444 -0
  3. model_loader.py +29 -0
  4. presentation_generator.py +246 -0
  5. 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