sohfossted commited on
Commit
edaaaf3
·
verified ·
1 Parent(s): d7b70cf

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +895 -0
  2. requirements.txt +5 -0
app.py ADDED
@@ -0,0 +1,895 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ LAB_MATH & LABHP - GÉNÉRATEUR DE PRÉSENTATIONS INTELLIGENT V3.0
3
+ All-in-one avec analyse automatique du type de document
4
+ """
5
+
6
+ import gradio as gr
7
+ import os
8
+ import re
9
+ import time
10
+ import nltk
11
+ from pptx import Presentation
12
+ from pptx.util import Inches, Pt
13
+ from pptx.dml.color import RGBColor
14
+ from pptx.enum.text import PP_ALIGN
15
+ from pptx.enum.shapes import MSO_SHAPE
16
+ import PyPDF2
17
+ from docx import Document
18
+ from collections import Counter
19
+ from datetime import datetime
20
+ import random
21
+
22
+ # ==========================================
23
+ # CONFIGURATION ET CONSTANTES
24
+ # ==========================================
25
+ CODE_SECRET = "32015"
26
+ VERSION = "3.0"
27
+
28
+ # Palettes de couleurs par type de document
29
+ THEMES = {
30
+ "scientifique": {
31
+ "primary": RGBColor(0, 84, 159), # Bleu profond
32
+ "secondary": RGBColor(142, 186, 229), # Bleu clair
33
+ "accent": RGBColor(255, 199, 44), # Or
34
+ "bg_title": RGBColor(0, 32, 96), # Bleu nuit
35
+ "title_text": RGBColor(255, 255, 255), # Blanc
36
+ "body_text": RGBColor(35, 35, 35), # Gris foncé
37
+ "icon": "🔬"
38
+ },
39
+ "pedagogique": {
40
+ "primary": RGBColor(39, 174, 96), # Vert
41
+ "secondary": RGBColor(46, 204, 113), # Vert clair
42
+ "accent": RGBColor(241, 196, 15), # Jaune
43
+ "bg_title": RGBColor(27, 79, 114), # Bleu-vert
44
+ "title_text": RGBColor(255, 255, 255), # Blanc
45
+ "body_text": RGBColor(35, 35, 35), # Gris foncé
46
+ "icon": "📚"
47
+ },
48
+ "professionnel": {
49
+ "primary": RGBColor(52, 73, 94), # Gris anthracite
50
+ "secondary": RGBColor(127, 140, 141), # Gris moyen
51
+ "accent": RGBColor(230, 126, 34), # Orange
52
+ "bg_title": RGBColor(44, 62, 80), # Bleu-gris
53
+ "title_text": RGBColor(255, 255, 255), # Blanc
54
+ "body_text": RGBColor(52, 73, 94), # Gris anthracite
55
+ "icon": "💼"
56
+ },
57
+ "rapport": {
58
+ "primary": RGBColor(155, 89, 182), # Violet
59
+ "secondary": RGBColor(142, 68, 173), # Violet foncé
60
+ "accent": RGBColor(52, 152, 219), # Bleu
61
+ "bg_title": RGBColor(91, 44, 111), # Violet profond
62
+ "title_text": RGBColor(255, 255, 255), # Blanc
63
+ "body_text": RGBColor(35, 35, 35), # Gris foncé
64
+ "icon": "📊"
65
+ },
66
+ "reunion": {
67
+ "primary": RGBColor(231, 76, 60), # Rouge
68
+ "secondary": RGBColor(192, 57, 43), # Rouge foncé
69
+ "accent": RGBColor(243, 156, 18), # Orange
70
+ "bg_title": RGBColor(169, 50, 38), # Bordeaux
71
+ "title_text": RGBColor(255, 255, 255), # Blanc
72
+ "body_text": RGBColor(35, 35, 35), # Gris foncé
73
+ "icon": "📋"
74
+ }
75
+ }
76
+
77
+ # ==========================================
78
+ # PARTIE 1 : ANALYSEUR INTELLIGENT DE DOCUMENTS
79
+ # ==========================================
80
+ class DocumentAnalyzer:
81
+ def __init__(self):
82
+ try:
83
+ nltk.download('punkt', quiet=True)
84
+ nltk.download('punkt_tab', quiet=True)
85
+ nltk.download('averaged_perceptron_tagger', quiet=True)
86
+ nltk.download('stopwords', quiet=True)
87
+ from nltk.corpus import stopwords
88
+ self.stopwords = set(stopwords.words('french'))
89
+ except:
90
+ self.stopwords = set(['le', 'la', 'les', 'de', 'du', 'des', 'un', 'une', 'et', 'est', 'sont'])
91
+
92
+ def extract_text(self, file_obj):
93
+ """Extrait le texte du fichier"""
94
+ try:
95
+ if file_obj.name.endswith('.pdf'):
96
+ reader = PyPDF2.PdfReader(file_obj.name)
97
+ text = " ".join([p.extract_text() for p in reader.pages if p.extract_text()])
98
+ elif file_obj.name.endswith('.docx'):
99
+ doc = Document(file_obj.name)
100
+ text = " ".join([p.text for p in doc.paragraphs])
101
+ else:
102
+ with open(file_obj.name, 'r', encoding='utf-8', errors='ignore') as f:
103
+ text = f.read()
104
+ return text
105
+ except Exception as e:
106
+ return f"Erreur d'extraction: {e}"
107
+
108
+ def detect_document_type(self, text):
109
+ """
110
+ Analyse le texte pour déterminer automatiquement le type de document
111
+ Retourne: (type_document, confiance, caractéristiques)
112
+ """
113
+ text_lower = text.lower()
114
+
115
+ # Mots-clés par catégorie
116
+ keywords = {
117
+ "Article Scientifique": {
118
+ "mots": [
119
+ "étude", "recherche", "méthodologie", "résultats", "analyse",
120
+ "données", "statistiques", "hypothèse", "expérience", "corrélation",
121
+ "significatif", "variable", "échantillon", "protocole", "publication",
122
+ "revue", "littérature", "bibliographie", "références", "abstract"
123
+ ],
124
+ "poids": 0
125
+ },
126
+ "Cours / Pédagogique": {
127
+ "mots": [
128
+ "cours", "leçon", "chapitre", "exercice", "apprentissage",
129
+ "étudiant", "professeur", "comprendre", "définition", "exemple",
130
+ "objectifs", "compétences", "évaluation", "notion", "principe",
131
+ "illustration", "démonstration", "pratique", "théorique", "savoir"
132
+ ],
133
+ "poids": 0
134
+ },
135
+ "Rapport d'Étude": {
136
+ "mots": [
137
+ "rapport", "étude", "analyse", "données", "collecte",
138
+ "terrain", "enquête", "observation", "constat", "recommandation",
139
+ "conclusion", "objectifs", "méthodologie", "échantillon", "résultats",
140
+ "indicateurs", "graphique", "tableau", "synthèse", "préconisation"
141
+ ],
142
+ "poids": 0
143
+ },
144
+ "Rapport de Réunion": {
145
+ "mots": [
146
+ "réunion", "compte-rendu", "participants", "ordre du jour", "discussion",
147
+ "décision", "action", "point", "échange", "prochain", "date",
148
+ "présents", "excusés", "délibération", "vote", "validation",
149
+ "proposition", "adoption", "calendrier", "responsable", "échéance"
150
+ ],
151
+ "poids": 0
152
+ },
153
+ "Communication / Présentation": {
154
+ "mots": [
155
+ "présentation", "communication", "conférence", "intervention", "public",
156
+ "message", "impact", "perspectives", "enjeux", "défis",
157
+ "opportunités", "vision", "stratégie", "innovation", "projet",
158
+ "partenaires", "collaboration", "développement", "avenir", "objectif"
159
+ ],
160
+ "poids": 0
161
+ },
162
+ "Thèse / Mémoire": {
163
+ "mots": [
164
+ "thèse", "mémoire", "doctorat", "master", "recherche",
165
+ "problématique", "hypothèse", "cadre théorique", "état de l'art",
166
+ "contribution", "originalité", "discussion", "limites", "perspectives",
167
+ "soutenance", "directeur", "jury", "travaux", "publications"
168
+ ],
169
+ "poids": 0
170
+ }
171
+ }
172
+
173
+ # Calcul des scores
174
+ sentences = text.split('.')
175
+ total_words = len(text.split())
176
+
177
+ for doc_type, data in keywords.items():
178
+ score = 0
179
+ for mot in data["mots"]:
180
+ occurrences = text_lower.count(mot)
181
+ if occurrences > 0:
182
+ score += occurrences * (1 + (len(mot) / 10))
183
+ data["poids"] = score
184
+
185
+ # Sélection du type avec le score le plus élevé
186
+ best_type = max(keywords.items(), key=lambda x: x[1]["poids"])
187
+ confidence = (best_type[1]["poids"] / max(1, total_words)) * 100
188
+
189
+ # Analyse complémentaire
190
+ features = self.extract_features(text)
191
+
192
+ return best_type[0], min(confidence, 100), features
193
+
194
+ def extract_features(self, text):
195
+ """Extrait des caractéristiques du document"""
196
+ features = {}
197
+
198
+ # Nombre de mots
199
+ words = text.split()
200
+ features["word_count"] = len(words)
201
+
202
+ # Nombre de phrases
203
+ sentences = text.split('.')
204
+ features["sentence_count"] = len([s for s in sentences if len(s.strip()) > 10])
205
+
206
+ # Présence de chiffres/nombres
207
+ numbers = re.findall(r'\d+', text)
208
+ features["number_count"] = len(numbers)
209
+
210
+ # Pourcentage de chiffres
211
+ if features["word_count"] > 0:
212
+ features["numeric_density"] = (features["number_count"] / features["word_count"]) * 100
213
+
214
+ # Longueur moyenne des phrases
215
+ if features["sentence_count"] > 0:
216
+ features["avg_sentence_length"] = features["word_count"] / features["sentence_count"]
217
+
218
+ return features
219
+
220
+ def extract_key_points(self, text, num_points=10):
221
+ """Extrait les points clés du document"""
222
+ sentences = nltk.sent_tokenize(text) if 'nltk' in dir() else text.split('.')
223
+
224
+ # Filtrer les phrases trop courtes
225
+ sentences = [s.strip() for s in sentences if len(s.strip()) > 30]
226
+
227
+ if not sentences:
228
+ return ["Document trop court pour l'analyse"]
229
+
230
+ # Score basé sur la position et les mots-clés
231
+ scored_sentences = []
232
+ for i, sent in enumerate(sentences):
233
+ score = 0
234
+ # Bonus pour les débuts de paragraphes
235
+ if i % 5 == 0:
236
+ score += 3
237
+ # Bonus pour les phrases contenant des mots importants
238
+ important_words = ['important', 'conclusion', 'résultat', 'objectif', 'but']
239
+ for word in important_words:
240
+ if word in sent.lower():
241
+ score += 2
242
+ # Malus pour les phrases trop longues
243
+ if len(sent.split()) > 30:
244
+ score -= 1
245
+
246
+ scored_sentences.append((score, sent))
247
+
248
+ # Trier et prendre les meilleures phrases
249
+ scored_sentences.sort(reverse=True)
250
+ key_points = [sent for score, sent in scored_sentences[:num_points]]
251
+
252
+ return key_points
253
+
254
+ # ==========================================
255
+ # PARTIE 2 : GÉNÉRATEUR DE PLANS ADAPTATIFS
256
+ # ==========================================
257
+ class AdaptiveOutlineGenerator:
258
+ def __init__(self):
259
+ self.templates = {
260
+ "Article Scientifique": {
261
+ "structure": [
262
+ ("title", "TITRE DE L'ARTICLE"),
263
+ ("authors", "AUTEURS & AFFILIATIONS"),
264
+ ("abstract", "RÉSUMÉ"),
265
+ ("intro", "INTRODUCTION & CONTEXTE"),
266
+ ("problem", "PROBLÉMATIQUE"),
267
+ ("methodology", "MÉTHODOLOGIE"),
268
+ ("results", "RÉSULTATS"),
269
+ ("discussion", "DISCUSSION"),
270
+ ("conclusion", "CONCLUSION"),
271
+ ("references", "RÉFÉRENCES CLÉS")
272
+ ],
273
+ "type": "scientifique",
274
+ "icon": "📄"
275
+ },
276
+ "Cours / Pédagogique": {
277
+ "structure": [
278
+ ("title", "TITRE DU COURS"),
279
+ ("objectives", "OBJECTIFS PÉDAGOGIQUES"),
280
+ ("prerequis", "PRÉ-REQUIS"),
281
+ ("introduction", "INTRODUCTION"),
282
+ ("part1", "PARTIE 1 : CONCEPTS FONDAMENTAUX"),
283
+ ("part2", "PARTIE 2 : APPLICATIONS"),
284
+ ("part3", "PARTIE 3 : EXERCICES TYPES"),
285
+ ("summary", "SYNTHÈSE"),
286
+ ("evaluation", "ÉVALUATION"),
287
+ ("resources", "RESSOURCES COMPLÉMENTAIRES")
288
+ ],
289
+ "type": "pedagogique",
290
+ "icon": "📖"
291
+ },
292
+ "Rapport d'Étude": {
293
+ "structure": [
294
+ ("title", "TITRE DU RAPPORT"),
295
+ ("exec_summary", "RÉSUMÉ EXÉCUTIF"),
296
+ ("context", "CONTEXTE DE L'ÉTUDE"),
297
+ ("objectives", "OBJECTIFS"),
298
+ ("methodology", "MÉTHODOLOGIE"),
299
+ ("results", "RÉSULTATS PRINCIPAUX"),
300
+ ("analysis", "ANALYSE DÉTAILLÉE"),
301
+ ("conclusions", "CONCLUSIONS"),
302
+ ("recommendations", "RECOMMANDATIONS"),
303
+ ("annexes", "ANNEXES")
304
+ ],
305
+ "type": "rapport",
306
+ "icon": "📊"
307
+ },
308
+ "Rapport de Réunion": {
309
+ "structure": [
310
+ ("title", "COMPTE-RENDU DE RÉUNION"),
311
+ ("info", "INFORMATIONS GÉNÉRALES"),
312
+ ("participants", "PARTICIPANTS"),
313
+ ("agenda", "ORDRE DU JOUR"),
314
+ ("discussions", "POINTS DISCUTÉS"),
315
+ ("decisions", "DÉCISIONS PRISES"),
316
+ ("actions", "PLAN D'ACTIONS"),
317
+ ("next", "PROCHAINE RÉUNION"),
318
+ ("annexes", "ANNEXES"),
319
+ ("signatures", "VALIDATION")
320
+ ],
321
+ "type": "reunion",
322
+ "icon": "📋"
323
+ },
324
+ "Communication / Présentation": {
325
+ "structure": [
326
+ ("title", "TITRE DE LA COMMUNICATION"),
327
+ ("context", "CONTEXTE"),
328
+ ("enjeux", "ENJEUX"),
329
+ ("message", "MESSAGE CLÉ"),
330
+ ("developpement", "DÉVELOPPEMENT"),
331
+ ("illustrations", "ILLUSTRATIONS"),
332
+ ("impacts", "IMPACTS"),
333
+ ("perspectives", "PERSPECTIVES"),
334
+ ("conclusion", "CONCLUSION"),
335
+ ("discussion", "DISCUSSION")
336
+ ],
337
+ "type": "professionnel",
338
+ "icon": "🎤"
339
+ },
340
+ "Thèse / Mémoire": {
341
+ "structure": [
342
+ ("title", "TITRE DE LA THÈSE"),
343
+ ("dedicace", "DÉDICACES"),
344
+ ("remerciements", "REMERCIEMENTS"),
345
+ ("resume", "RÉSUMÉ"),
346
+ ("abstract", "ABSTRACT"),
347
+ ("intro", "INTRODUCTION GÉNÉRALE"),
348
+ ("chap1", "CHAPITRE 1 : REVUE DE LITTÉRATURE"),
349
+ ("chap2", "CHAPITRE 2 : CADRE THÉORIQUE"),
350
+ ("chap3", "CHAPITRE 3 : MÉTHODOLOGIE"),
351
+ ("chap4", "CHAPITRE 4 : RÉSULTATS"),
352
+ ("chap5", "CHAPITRE 5 : DISCUSSION"),
353
+ ("conclusion", "CONCLUSION GÉNÉRALE"),
354
+ ("bibliographie", "BIBLIOGRAPHIE"),
355
+ ("annexes", "ANNEXES")
356
+ ],
357
+ "type": "scientifique",
358
+ "icon": "🎓"
359
+ }
360
+ }
361
+
362
+ def generate_outline(self, text, detected_type, features, key_points):
363
+ """Génère un plan adapté au type de document"""
364
+ template = self.templates.get(detected_type, self.templates["Rapport d'Étude"])
365
+
366
+ # Création du titre principal
367
+ sentences = text.split('.')
368
+ title = sentences[0][:100] if sentences else f"DOCUMENT: {detected_type}"
369
+ title = re.sub(r'[^\w\s-]', '', title).strip()
370
+
371
+ outline = {
372
+ "title": title,
373
+ "type": detected_type,
374
+ "theme": template["type"],
375
+ "icon": template["icon"],
376
+ "slides": []
377
+ }
378
+
379
+ # Slide de titre
380
+ outline["slides"].append({
381
+ "type": "title",
382
+ "title": title.upper(),
383
+ "subtitle": f"{detected_type} | LAB_MATH & LABHP\nGénéré le {datetime.now().strftime('%d/%m/%Y')}",
384
+ "icon": template["icon"]
385
+ })
386
+
387
+ # Génération des slides selon le template
388
+ for i, (slide_type, slide_title) in enumerate(template["structure"]):
389
+ slide_content = self.generate_slide_content(
390
+ slide_type, slide_title, text, key_points, features, i, len(template["structure"])
391
+ )
392
+
393
+ outline["slides"].append({
394
+ "type": "content",
395
+ "title": slide_title,
396
+ "content": slide_content,
397
+ "icon": self.get_slide_icon(slide_type)
398
+ })
399
+
400
+ # Slide de remerciements
401
+ outline["slides"].append({
402
+ "type": "thanks",
403
+ "title": "MERCI DE VOTRE ATTENTION",
404
+ "content": [
405
+ "N'hésitez pas à poser vos questions",
406
+ "📧 Contact: labmath@cie-label-bertoua.cm",
407
+ f"📊 Document de type: {detected_type}",
408
+ "✨ Généré par l'IA de LAB_MATH"
409
+ ],
410
+ "icon": "🙏"
411
+ })
412
+
413
+ return outline
414
+
415
+ def generate_slide_content(self, slide_type, slide_title, text, key_points, features, index, total):
416
+ """Génère le contenu spécifique pour chaque type de slide"""
417
+
418
+ # Adapter le contenu selon le type de slide
419
+ if "intro" in slide_type or "context" in slide_type:
420
+ return self._generate_intro_content(text, key_points[:2])
421
+
422
+ elif "method" in slide_type:
423
+ return self._generate_methodology_content(text)
424
+
425
+ elif "result" in slide_type or "analysis" in slide_type:
426
+ return self._generate_results_content(text, features)
427
+
428
+ elif "conclu" in slide_type or "summary" in slide_type:
429
+ return self._generate_conclusion_content(text, key_points[-3:])
430
+
431
+ elif "action" in slide_type or "recommend" in slide_type:
432
+ return self._generate_recommendations_content(text)
433
+
434
+ elif "objectives" in slide_type:
435
+ return self._generate_objectives_content(text)
436
+
437
+ else:
438
+ # Contenu générique
439
+ return self._generate_generic_content(text, key_points, index, total)
440
+
441
+ def get_slide_icon(self, slide_type):
442
+ """Retourne une icône adaptée au type de slide"""
443
+ icons = {
444
+ "intro": "🔍",
445
+ "method": "⚙️",
446
+ "result": "📈",
447
+ "conclu": "✅",
448
+ "action": "🚀",
449
+ "objectives": "🎯",
450
+ "context": "🌍",
451
+ "analysis": "📊",
452
+ "discussion": "💭",
453
+ "references": "📚",
454
+ "annexes": "📎"
455
+ }
456
+
457
+ for key, icon in icons.items():
458
+ if key in slide_type:
459
+ return icon
460
+ return "•"
461
+
462
+ def _generate_intro_content(self, text, key_points):
463
+ content = ["Contexte et introduction :"]
464
+ if key_points:
465
+ content.extend([f"• {kp[:100]}..." for kp in key_points])
466
+ else:
467
+ content.append("• Présentation du sujet et de son importance")
468
+ content.append("• Objectifs généraux du document")
469
+ return content
470
+
471
+ def _generate_methodology_content(self, text):
472
+ # Chercher des sections de méthodologie dans le texte
473
+ method_section = re.search(r'(méthodologie|méthode|approche|protocole).*?(?=\n\n|\Z)',
474
+ text, re.IGNORECASE | re.DOTALL)
475
+
476
+ if method_section:
477
+ sentences = method_section.group().split('.')
478
+ return [f"• {s.strip()}" for s in sentences[:4] if len(s.strip()) > 20]
479
+
480
+ return [
481
+ "• Approche méthodologique adoptée",
482
+ "• Outils et techniques utilisés",
483
+ "• Collecte et analyse des données",
484
+ "• Validation des résultats"
485
+ ]
486
+
487
+ def _generate_results_content(self, text, features):
488
+ content = ["Résultats principaux :"]
489
+
490
+ # Chercher des chiffres dans le texte
491
+ numbers = re.findall(r'\d+%|\d+[.,]?\d*\s*(?:participants|personnes|échantillons)', text)
492
+
493
+ if numbers:
494
+ content.append(f"• {numbers[0] if numbers else 'Données quantitatives disponibles'}")
495
+
496
+ if features.get("numeric_density", 0) > 5:
497
+ content.append("• Analyse statistique significative")
498
+
499
+ content.append("• Interprétation des données")
500
+ content.append("• Implications des résultats")
501
+
502
+ return content
503
+
504
+ def _generate_conclusion_content(self, text, key_points):
505
+ content = ["Synthèse et conclusions :"]
506
+ if key_points:
507
+ content.extend([f"• {kp[:100]}..." for kp in key_points])
508
+ else:
509
+ content.append("• Récapitulatif des points clés")
510
+ content.append("• Contributions principales")
511
+ content.append("• Limites et perspectives")
512
+ return content
513
+
514
+ def _generate_recommendations_content(self, text):
515
+ # Chercher des recommandations
516
+ rec_section = re.search(r'(recommandation|préconisation|proposition|suggestion).*?(?=\n\n|\Z)',
517
+ text, re.IGNORECASE | re.DOTALL)
518
+
519
+ if rec_section:
520
+ sentences = rec_section.group().split('.')
521
+ return [f"✓ {s.strip()}" for s in sentences[:4] if len(s.strip()) > 15]
522
+
523
+ return [
524
+ "✓ Action prioritaire 1",
525
+ "✓ Action prioritaire 2",
526
+ "✓ Action prioritaire 3",
527
+ "✓ Calendrier de mise en œuvre"
528
+ ]
529
+
530
+ def _generate_objectives_content(self, text):
531
+ content = ["Objectifs :"]
532
+ objectives = re.findall(r'(?:objectif|but|finalité)\s*(?:de|du|des)?\s*([^\.]+)',
533
+ text, re.IGNORECASE)
534
+
535
+ if objectives:
536
+ content.extend([f"🎯 {obj.strip()}" for obj in objectives[:4]])
537
+ else:
538
+ content.extend([
539
+ "🎯 Objectif général 1",
540
+ "🎯 Objectif spécifique 1",
541
+ "🎯 Objectif spécifique 2",
542
+ "🎯 Résultats attendus"
543
+ ])
544
+
545
+ return content
546
+
547
+ def _generate_generic_content(self, text, key_points, index, total):
548
+ """Génère du contenu générique"""
549
+ if key_points and index < len(key_points):
550
+ main_point = key_points[index]
551
+ return [f"• {main_point[:150]}...", "• Développement du point", "• Illustrations et exemples"]
552
+
553
+ return [f"• Point clé {index+1}", "• Développement", "• Conclusion partielle"]
554
+
555
+ # ==========================================
556
+ # PARTIE 3 : GÉNÉRATEUR DE PRÉSENTATION PROFESSIONNELLE
557
+ # ==========================================
558
+ class ProfessionalPresentationGenerator:
559
+ def __init__(self):
560
+ self.themes = THEMES
561
+
562
+ def generate(self, outline):
563
+ """Génère la présentation PowerPoint"""
564
+ prs = Presentation()
565
+ prs.slide_width = Inches(13.33)
566
+ prs.slide_height = Inches(7.5)
567
+
568
+ # Récupérer le thème approprié
569
+ theme = self.themes.get(outline["theme"], self.themes["professionnel"])
570
+
571
+ for slide_data in outline["slides"]:
572
+ if slide_data["type"] == "title":
573
+ self._create_title_slide(prs, slide_data, theme)
574
+ elif slide_data["type"] == "thanks":
575
+ self._create_thanks_slide(prs, slide_data, theme)
576
+ else:
577
+ self._create_content_slide(prs, slide_data, theme, outline)
578
+
579
+ # Sauvegarde
580
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
581
+ filename = f"Presentation_LabMath_{timestamp}.pptx"
582
+ prs.save(filename)
583
+ return filename
584
+
585
+ def _create_title_slide(self, prs, data, theme):
586
+ """Crée la slide de titre"""
587
+ slide = prs.slides.add_slide(prs.slide_layouts[6])
588
+
589
+ # Fond
590
+ bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height)
591
+ bg.fill.solid()
592
+ bg.fill.fore_color.rgb = theme["bg_title"]
593
+ bg.line.fill.background()
594
+
595
+ # Bandeau décoratif
596
+ band = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, prs.slide_height - Inches(1),
597
+ prs.slide_width, Inches(1))
598
+ band.fill.solid()
599
+ band.fill.fore_color.rgb = theme["accent"]
600
+ band.fill.transparency = 0.3
601
+ band.line.fill.background()
602
+
603
+ # Titre principal
604
+ title_box = slide.shapes.add_textbox(Inches(1), Inches(2), prs.slide_width - Inches(2), Inches(2))
605
+ title_frame = title_box.text_frame
606
+ title_frame.word_wrap = True
607
+
608
+ # Icône
609
+ icon_para = title_frame.add_paragraph()
610
+ icon_para.text = data.get("icon", "📊")
611
+ icon_para.font.size = Pt(60)
612
+ icon_para.font.color.rgb = theme["accent"]
613
+ icon_para.alignment = PP_ALIGN.CENTER
614
+
615
+ # Titre
616
+ title_para = title_frame.add_paragraph()
617
+ title_para.text = data["title"]
618
+ title_para.font.size = Pt(44)
619
+ title_para.font.bold = True
620
+ title_para.font.color.rgb = theme["title_text"]
621
+ title_para.alignment = PP_ALIGN.CENTER
622
+ title_para.space_before = Pt(20)
623
+
624
+ # Sous-titre
625
+ if "subtitle" in data:
626
+ sub_box = slide.shapes.add_textbox(Inches(1), Inches(5), prs.slide_width - Inches(2), Inches(1))
627
+ sub_frame = sub_box.text_frame
628
+ sub_para = sub_frame.add_paragraph()
629
+ sub_para.text = data["subtitle"]
630
+ sub_para.font.size = Pt(18)
631
+ sub_para.font.color.rgb = theme["title_text"]
632
+ sub_para.alignment = PP_ALIGN.CENTER
633
+
634
+ def _create_content_slide(self, prs, data, theme, outline):
635
+ """Crée une slide de contenu"""
636
+ slide = prs.slides.add_slide(prs.slide_layouts[6])
637
+
638
+ # Bandeau supérieur
639
+ header = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, Inches(0.12))
640
+ header.fill.solid()
641
+ header.fill.fore_color.rgb = theme["primary"]
642
+ header.line.fill.background()
643
+
644
+ # Bande latérale décorative
645
+ side = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, Inches(0.08), prs.slide_height)
646
+ side.fill.solid()
647
+ side.fill.fore_color.rgb = theme["accent"]
648
+ side.line.fill.background()
649
+
650
+ # Titre de la slide
651
+ title_box = slide.shapes.add_textbox(Inches(0.5), Inches(0.2), prs.slide_width - Inches(1), Inches(0.8))
652
+ title_frame = title_box.text_frame
653
+ title_para = title_frame.add_paragraph()
654
+
655
+ # Icône + titre
656
+ title_para.text = f"{data.get('icon', '•')} {data['title']}"
657
+ title_para.font.size = Pt(28)
658
+ title_para.font.bold = True
659
+ title_para.font.color.rgb = theme["primary"]
660
+
661
+ # Contenu
662
+ content_box = slide.shapes.add_textbox(Inches(0.8), Inches(1.2),
663
+ prs.slide_width - Inches(1.5),
664
+ prs.slide_height - Inches(2.5))
665
+ content_frame = content_box.text_frame
666
+ content_frame.word_wrap = True
667
+
668
+ for item in data.get("content", []):
669
+ para = content_frame.add_paragraph()
670
+ para.text = item
671
+ para.font.size = Pt(18)
672
+ para.font.color.rgb = theme["body_text"]
673
+ para.space_after = Pt(12)
674
+ para.space_before = Pt(6)
675
+
676
+ # Mettre en évidence les éléments importants
677
+ if item.startswith("✓") or item.startswith("🎯"):
678
+ para.font.bold = True
679
+ para.font.color.rgb = theme["primary"]
680
+
681
+ # Pied de page
682
+ footer = slide.shapes.add_textbox(Inches(0.5), prs.slide_height - Inches(0.4),
683
+ prs.slide_width - Inches(1), Inches(0.3))
684
+ footer_frame = footer.text_frame
685
+ footer_para = footer_frame.add_paragraph()
686
+ footer_para.text = f"LAB_MATH & LABHP | {outline['type']} | Slide {len(prs.slides)}"
687
+ footer_para.font.size = Pt(10)
688
+ footer_para.font.color.rgb = RGBColor(150, 150, 150)
689
+ footer_para.alignment = PP_ALIGN.RIGHT
690
+
691
+ def _create_thanks_slide(self, prs, data, theme):
692
+ """Crée la slide de remerciements"""
693
+ slide = prs.slides.add_slide(prs.slide_layouts[6])
694
+
695
+ # Fond
696
+ bg = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, prs.slide_height)
697
+ bg.fill.solid()
698
+ bg.fill.fore_color.rgb = RGBColor(245, 245, 245)
699
+ bg.line.fill.background()
700
+
701
+ # Message principal
702
+ title_box = slide.shapes.add_textbox(Inches(1), Inches(2), prs.slide_width - Inches(2), Inches(1.5))
703
+ title_frame = title_box.text_frame
704
+ title_para = title_frame.add_paragraph()
705
+ title_para.text = data["title"]
706
+ title_para.font.size = Pt(48)
707
+ title_para.font.bold = True
708
+ title_para.font.color.rgb = theme["primary"]
709
+ title_para.alignment = PP_ALIGN.CENTER
710
+
711
+ # Contenu
712
+ content_box = slide.shapes.add_textbox(Inches(2), Inches(4), prs.slide_width - Inches(4), Inches(2))
713
+ content_frame = content_box.text_frame
714
+ content_frame.word_wrap = True
715
+
716
+ for item in data.get("content", []):
717
+ para = content_frame.add_paragraph()
718
+ para.text = item
719
+ para.font.size = Pt(20)
720
+ para.font.color.rgb = theme["body_text"]
721
+ para.alignment = PP_ALIGN.CENTER
722
+ para.space_after = Pt(10)
723
+
724
+ # ==========================================
725
+ # PARTIE 4 : INTERFACE PRINCIPALE
726
+ # ==========================================
727
+ class PresentationApp:
728
+ def __init__(self):
729
+ self.analyzer = DocumentAnalyzer()
730
+ self.outline_generator = AdaptiveOutlineGenerator()
731
+ self.presentation_generator = ProfessionalPresentationGenerator()
732
+ self.code_secret = CODE_SECRET
733
+
734
+ def authenticate(self, code):
735
+ """Vérifie le code d'accès"""
736
+ if code == self.code_secret:
737
+ return gr.update(visible=False), gr.update(visible=True), "✅ Accès autorisé !"
738
+ return gr.update(visible=True), gr.update(visible=False), "❌ Code incorrect"
739
+
740
+ def process_document(self, file, code):
741
+ """Traite le document et génère la présentation"""
742
+ # Vérification du code
743
+ if code != self.code_secret:
744
+ return None, "❌ Code d'accès invalide", "", "", ""
745
+
746
+ if not file:
747
+ return None, "❌ Veuillez sélectionner un fichier", "", "", ""
748
+
749
+ try:
750
+ # Étape 1: Extraction du texte
751
+ status = "📖 Extraction du texte..."
752
+ text = self.analyzer.extract_text(file)
753
+
754
+ if not text or len(text) < 100:
755
+ return None, "❌ Document trop court ou illisible", "", "", ""
756
+
757
+ # Étape 2: Analyse du type de document
758
+ status = "🔍 Analyse du type de document..."
759
+ doc_type, confidence, features = self.analyzer.detect_document_type(text)
760
+
761
+ # Étape 3: Extraction des points clés
762
+ status = "🎯 Extraction des points clés..."
763
+ key_points = self.analyzer.extract_key_points(text)
764
+
765
+ # Étape 4: Génération du plan adaptatif
766
+ status = "📝 Génération du plan adaptatif..."
767
+ outline = self.outline_generator.generate_outline(text, doc_type, features, key_points)
768
+
769
+ # Étape 5: Génération de la présentation
770
+ status = "🎨 Création de la présentation..."
771
+ pptx_file = self.presentation_generator.generate(outline)
772
+
773
+ # Informations pour l'affichage
774
+ doc_info = f"📊 Type détecté: {doc_type}\n📈 Confiance: {confidence:.1f}%\n📄 Mots: {features['word_count']}"
775
+
776
+ plan_info = "Plan généré:\n" + "\n".join([f"• {s['title']}" for s in outline['slides'][:5]])
777
+
778
+ preview = f"Points clés extraits:\n" + "\n".join([f"• {kp[:80]}..." for kp in key_points[:3]])
779
+
780
+ return pptx_file, f"✅ Succès! Présentation générée", doc_info, plan_info, preview
781
+
782
+ except Exception as e:
783
+ return None, f"❌ Erreur: {str(e)}", "", "", ""
784
+
785
+ # ==========================================
786
+ # LANCEMENT DE L'APPLICATION
787
+ # ==========================================
788
+ app = PresentationApp()
789
+
790
+ with gr.Blocks(title="LAB_MATH - Générateur Intelligent de Présentations",
791
+ theme=gr.themes.Soft()) as demo:
792
+
793
+ gr.Markdown("""
794
+ # 🎯 LAB_MATH & LABHP - Générateur Intelligent de Présentations V3.0
795
+ ### Analyse automatique du type de document et génération adaptative
796
+ """)
797
+
798
+ # Écran de connexion
799
+ with gr.Column(visible=True) as auth_screen:
800
+ gr.Markdown("""
801
+ <div style='text-align: center; padding: 50px;'>
802
+ <h1 style='color: #2E86AB;'>🔐 ACCÈS SÉCURISÉ</h1>
803
+ <p>Veuillez entrer votre code d'accès pour utiliser l'application</p>
804
+ </div>
805
+ """)
806
+
807
+ code_input = gr.Textbox(label="Code d'accès", type="password",
808
+ placeholder="Entrez votre code")
809
+ auth_btn = gr.Button("Se connecter", variant="primary")
810
+ auth_status = gr.Textbox(label="Statut", interactive=False)
811
+
812
+ # Application principale
813
+ with gr.Column(visible=False) as main_screen:
814
+ gr.Markdown("""
815
+ <div style='background: linear-gradient(135deg, #2E86AB, #3498DB); padding: 20px; border-radius: 10px; color: white; text-align: center;'>
816
+ <h2 style='margin:0;'>🤖 Générateur Intelligent de Présentations</h2>
817
+ <p style='margin:5px 0 0 0; opacity:0.9;'>Analyse automatique | Plans adaptatifs | Design professionnel</p>
818
+ </div>
819
+ """)
820
+
821
+ with gr.Row():
822
+ with gr.Column(scale=1):
823
+ file_input = gr.File(label="📁 1. Sélectionnez votre document",
824
+ file_types=[".pdf", ".docx", ".txt"])
825
+
826
+ code_verify = gr.Textbox(label="🔑 Code de vérification",
827
+ type="password",
828
+ placeholder="Confirmez votre code")
829
+
830
+ generate_btn = gr.Button("🚀 GÉNÉRER LA PRÉSENTATION",
831
+ variant="primary", size="lg")
832
+
833
+ status_output = gr.Textbox(label="Statut", interactive=False)
834
+
835
+ with gr.Column(scale=2):
836
+ with gr.Tabs():
837
+ with gr.TabItem("📊 Analyse du document"):
838
+ doc_info = gr.Textbox(label="Type de document détecté",
839
+ lines=4, interactive=False)
840
+ key_points_preview = gr.Textbox(label="Points clés extraits",
841
+ lines=6, interactive=False)
842
+
843
+ with gr.TabItem("📝 Plan généré"):
844
+ plan_info = gr.Textbox(label="Structure adaptative",
845
+ lines=8, interactive=False)
846
+
847
+ with gr.TabItem("🎨 Aperçu"):
848
+ preview_area = gr.HTML("""
849
+ <div style='padding: 20px; text-align: center; color: #666;'>
850
+ <h3>Aperçu de la présentation</h3>
851
+ <p>Générez une présentation pour voir l'aperçu</p>
852
+ </div>
853
+ """)
854
+
855
+ with gr.Row():
856
+ with gr.Column(scale=1):
857
+ output_file = gr.File(label="📥 Télécharger la présentation")
858
+
859
+ with gr.Column(scale=2):
860
+ gr.Markdown("""
861
+ ### 📋 Types de documents supportés:
862
+ - Articles scientifiques
863
+ - Cours et supports pédagogiques
864
+ - Rapports d'étude
865
+ - Comptes-rendus de réunion
866
+ - Communications et présentations
867
+ - Thèses et mémoires
868
+ """)
869
+
870
+ # Gestion des événements
871
+ auth_btn.click(
872
+ fn=app.authenticate,
873
+ inputs=[code_input],
874
+ outputs=[auth_screen, main_screen, auth_status]
875
+ )
876
+
877
+ generate_btn.click(
878
+ fn=app.process_document,
879
+ inputs=[file_input, code_verify],
880
+ outputs=[output_file, status_output, doc_info, plan_info, key_points_preview]
881
+ )
882
+
883
+ if __name__ == "__main__":
884
+ print("=" * 60)
885
+ print("🚀 LAB_MATH - Générateur Intelligent de Présentations V3.0")
886
+ print("=" * 60)
887
+ print("📊 Analyse automatique des documents")
888
+ print("🎯 Plans adaptatifs par type de document")
889
+ print("✨ Design professionnel adapté")
890
+ print("=" * 60)
891
+ print(f"🔐 Code d'accès: {CODE_SECRET}")
892
+ print("🌐 Interface: http://localhost:7860")
893
+ print("=" * 60)
894
+
895
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=False)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ gradio
2
+ python-pptx
3
+ python-docx
4
+ PyPDF2
5
+ nltk