MisterAI commited on
Commit
ddf82c6
·
verified ·
1 Parent(s): 8562c55

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +421 -0
app.py ADDED
@@ -0,0 +1,421 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ from huggingface_hub import hf_hub_download, login
4
+ from transformers import AutoModelForCausalLM, AutoTokenizer, pipeline
5
+ from pptx import Presentation
6
+ from pptx.util import Inches, Pt
7
+ from pptx.enum.text import PP_ALIGN
8
+ import torch
9
+ from llama_cpp import Llama
10
+ import time
11
+ from PIL import Image
12
+ import io
13
+ import requests
14
+ from diffusers import FluxPipeline
15
+
16
+ # Configuration des modèles disponibles
17
+ TEXT_MODELS = {
18
+ "Mistral Nemo 2407 (GGUF)": "MisterAI/Bartowski_MistralAI_Mistral-Nemo-Instruct-2407-IQ4_XS.gguf",
19
+ "Mixtral 8x7B": "mistralai/Mixtral-8x7B-v0.1",
20
+ "Lucie 7B": "OpenLLM-France/Lucie-7B"
21
+ }
22
+
23
+ IMAGE_MODELS = {
24
+ "FLUX.1": "black-forest-labs/FLUX.1-schnell",
25
+ "ArtifyAI": "ImageInception/ArtifyAI-v1.1"
26
+ }
27
+
28
+ # Préprompt amélioré pour une meilleure structuration
29
+ PREPROMPT = """Vous êtes un assistant IA expert en création de présentations PowerPoint professionnelles.
30
+ Générez une présentation structurée et détaillée en suivant ce format EXACT:
31
+
32
+ TITRE: [Titre principal de la présentation]
33
+
34
+ DIAPO 1:
35
+ Titre: [Titre de la diapo]
36
+ Points:
37
+ - Point 1
38
+ - Point 2
39
+ - Point 3
40
+ Image: [Description détaillée de l'image souhaitée pour cette diapo. Soyez très précis dans la description pour permettre
41
+ une génération d'image de qualité. Par exemple : "Une illustration professionnelle montrant un concept clé de cybersécurité
42
+ avec des éléments visuels modernes, un style épuré et des couleurs corporate (fond noir, couleurs bleu electrique, rouge, gris, blanc).
43
+ L'image doit être claire, minimaliste et adaptée à une présentation professionnelle."]
44
+
45
+ DIAPO 2:
46
+ Titre: [Titre de la diapo]
47
+ Points:
48
+ - Point 1
49
+ - Point 2
50
+ - Point 3
51
+ Image: [Description détaillée de l'image souhaitée pour cette diapo]
52
+
53
+ [Continuez avec ce format pour chaque diapositive]
54
+
55
+ Analysez le texte suivant et créez une présentation professionnelle avec des descriptions d'images pertinentes :"""
56
+
57
+ class PresentationGenerator:
58
+ def __init__(self):
59
+ self.token = os.getenv('Authentification_HF')
60
+ if not self.token:
61
+ raise ValueError("Token d'authentification HuggingFace non trouvé")
62
+ login(self.token)
63
+ self.text_model = None
64
+ self.text_tokenizer = None
65
+ self.image_pipeline = None
66
+
67
+ def load_text_model(self, model_name):
68
+ """Charge le modèle de génération de texte"""
69
+ model_id = TEXT_MODELS[model_name]
70
+ if model_id.endswith('.gguf'):
71
+ # Configuration pour les modèles GGUF
72
+ model_path = hf_hub_download(
73
+ repo_id=model_id.split('/')[0] + '/' + model_id.split('/')[1],
74
+ filename=model_id.split('/')[-1],
75
+ token=self.token
76
+ )
77
+ self.text_model = Llama(
78
+ model_path=model_path,
79
+ n_ctx=4096,
80
+ n_batch=512,
81
+ verbose=False
82
+ )
83
+ else:
84
+ # Configuration pour les modèles Transformers standards
85
+ self.text_tokenizer = AutoTokenizer.from_pretrained(model_id, token=self.token)
86
+ self.text_model = AutoModelForCausalLM.from_pretrained(
87
+ model_id,
88
+ torch_dtype=torch.bfloat16,
89
+ device_map="auto",
90
+ token=self.token
91
+ )
92
+
93
+ def load_image_model(self, model_name):
94
+ """Charge le modèle de génération d'images"""
95
+ model_id = IMAGE_MODELS[model_name]
96
+ if model_id == "black-forest-labs/FLUX.1-schnell":
97
+ self.image_pipeline = FluxPipeline.from_pretrained(
98
+ model_id,
99
+ revision="refs/pr/1", # Utiliser une révision spécifique
100
+ torch_dtype=torch.bfloat16
101
+ )
102
+ self.image_pipeline.enable_model_cpu_offload() # Économise de la VRAM en déchargeant le modèle sur le CPU
103
+ self.image_pipeline.tokenizer.add_prefix_space = False # Désactive add_prefix_space
104
+ print(f"Modèle d'image FLUX chargé : {model_id}")
105
+ else:
106
+ self.image_pipeline = pipeline(
107
+ "text-to-image",
108
+ model=model_id,
109
+ token=self.token
110
+ )
111
+ print(f"Modèle d'image chargé : {model_id}")
112
+
113
+ def generate_text(self, prompt, temperature=0.7, max_tokens=4096):
114
+ """Génère le texte de la présentation"""
115
+ if isinstance(self.text_model, Llama):
116
+ response = self.text_model(
117
+ prompt,
118
+ max_tokens=max_tokens,
119
+ temperature=temperature,
120
+ echo=False
121
+ )
122
+ return response['choices'][0]['text']
123
+ else:
124
+ inputs = self.text_tokenizer.apply_chat_template(
125
+ [{"role": "user", "content": prompt}],
126
+ return_tensors="pt",
127
+ return_dict=True
128
+ )
129
+ outputs = self.text_model.generate(
130
+ **inputs,
131
+ max_new_tokens=max_tokens,
132
+ temperature=temperature
133
+ )
134
+ return self.text_tokenizer.decode(outputs[0], skip_special_tokens=True)
135
+
136
+ def generate_image(self, prompt, negative_prompt="", num_inference_steps=30):
137
+ """Génère une image pour la diapositive"""
138
+ try:
139
+ image = self.image_pipeline(
140
+ prompt=prompt,
141
+ negative_prompt=negative_prompt,
142
+ num_inference_steps=num_inference_steps,
143
+ guidance_scale=0.0,
144
+ max_sequence_length=256,
145
+ generator=torch.Generator("cpu").manual_seed(0)
146
+ ).images[0]
147
+ return image
148
+ except Exception as e:
149
+ print(f"Erreur lors de la génération de l'image: {str(e)}")
150
+ return None
151
+
152
+ def parse_presentation_content(self, content):
153
+ """Parse le contenu généré en sections pour les diapositives"""
154
+ slides = []
155
+ current_slide = None
156
+
157
+ for line in content.split('\n'):
158
+ line = line.strip()
159
+ if line.startswith('TITRE:'):
160
+ slides.append({'type': 'title', 'title': line[6:].strip()})
161
+ elif line.startswith('DIAPO'):
162
+ if current_slide:
163
+ slides.append(current_slide)
164
+ current_slide = {'type': 'content', 'title': '', 'points': [], 'image_prompt': ''}
165
+ elif line.startswith('Titre:') and current_slide:
166
+ current_slide['title'] = line[6:].strip()
167
+ elif line.startswith('- ') and current_slide:
168
+ current_slide['points'].append(line[2:].strip())
169
+ elif line.startswith('Image:') and current_slide:
170
+ current_slide['image_prompt'] = line[6:].strip()
171
+
172
+ if current_slide:
173
+ slides.append(current_slide)
174
+
175
+ return slides
176
+
177
+ def create_presentation(self, slides):
178
+ """Crée la présentation PowerPoint avec texte et images"""
179
+ prs = Presentation()
180
+
181
+ # Première diapo (titre)
182
+ title_slide = prs.slides.add_slide(prs.slide_layouts[0])
183
+ title_slide.shapes.title.text = slides[0]['title']
184
+
185
+ # Autres diapos
186
+ for slide in slides[1:]:
187
+ content_slide = prs.slides.add_slide(prs.slide_layouts[1])
188
+ content_slide.shapes.title.text = slide['title']
189
+
190
+ # Ajout du texte
191
+ if slide['points']:
192
+ body = content_slide.shapes.placeholders[1].text_frame
193
+ body.clear()
194
+ for point in slide['points']:
195
+ p = body.add_paragraph()
196
+ p.text = point
197
+ p.level = 0
198
+
199
+ # Ajout de l'image si disponible
200
+ if slide.get('image_prompt'):
201
+ image = self.generate_image(slide['image_prompt'])
202
+ if image:
203
+ # Sauvegarde temporaire de l'image
204
+ img_path = f"temp_slide_{slides.index(slide)}.png"
205
+ image.save(img_path)
206
+
207
+ # Ajout de l'image à la diapositive
208
+ left = Inches(1)
209
+ top = Inches(2.5)
210
+ content_slide.shapes.add_picture(img_path, left, top, height=Inches(4))
211
+
212
+ # Suppression du fichier temporaire
213
+ os.remove(img_path)
214
+
215
+ return prs
216
+
217
+ def generate_presentation_with_progress(text, text_model_name, image_model_name, temperature, max_tokens, negative_prompt):
218
+ """Fonction principale de génération avec suivi de progression"""
219
+ try:
220
+ start_time = time.time()
221
+ generator = PresentationGenerator()
222
+
223
+ # Chargement des modèles
224
+ yield "Chargement des modèles...", None, None
225
+ generator.load_text_model(text_model_name)
226
+ generator.load_image_model(image_model_name)
227
+
228
+ # Génération du contenu
229
+ yield "Génération du contenu de la présentation...", None, None
230
+ full_prompt = PREPROMPT + "\n\n" + text
231
+ generated_content = generator.generate_text(full_prompt, temperature, max_tokens)
232
+
233
+ # Création de la présentation
234
+ yield "Création de la présentation PowerPoint...", generated_content, None
235
+ slides = generator.parse_presentation_content(generated_content)
236
+ prs = generator.create_presentation(slides)
237
+
238
+ # Sauvegarde
239
+ output_path = "presentation.pptx"
240
+ prs.save(output_path)
241
+
242
+ execution_time = time.time() - start_time
243
+ status = f"Présentation générée avec succès en {execution_time:.2f} secondes!"
244
+
245
+ return status, generated_content, output_path
246
+
247
+ except Exception as e:
248
+ return f"Erreur: {str(e)}", None, None
249
+
250
+ # CSS personnalisé pour un thème sombre amélioré
251
+ css = """
252
+ /* Thème sombre personnalisé */
253
+ .gradio-container {
254
+ background-color: #000000 !important;
255
+ color: #ffffff !important;
256
+ }
257
+
258
+ .gr-form, .gr-box, .gr-panel {
259
+ border-radius: 8px !important;
260
+ background-color: #1a1a1a !important;
261
+ border: 1px solid #333333 !important;
262
+ color: #ffffff !important;
263
+ }
264
+
265
+ .gr-input, .gr-textarea, .gr-dropdown {
266
+ background-color: #2d2d2d !important;
267
+ color: #ffffff !important;
268
+ border: 1px solid #404040 !important;
269
+ }
270
+
271
+ .gr-button {
272
+ background-color: #2d2d2d !important;
273
+ color: #ffffff !important;
274
+ border: 1px solid #404040 !important;
275
+ transition: all 0.3s ease !important;
276
+ }
277
+
278
+ .gr-button:hover {
279
+ background-color: #404040 !important;
280
+ transform: translateY(-2px) !important;
281
+ }
282
+
283
+ /* Textes et labels */
284
+ h1, h2, h3, p, label, .gr-text {
285
+ color: #ffffff !important;
286
+ }
287
+
288
+ /* Scrollbar */
289
+ ::-webkit-scrollbar {
290
+ width: 8px;
291
+ height: 8px;
292
+ }
293
+
294
+ ::-webkit-scrollbar-track {
295
+ background: #1a1a1a;
296
+ }
297
+
298
+ ::-webkit-scrollbar-thumb {
299
+ background: #404040;
300
+ border-radius: 4px;
301
+ }
302
+
303
+ ::-webkit-scrollbar-thumb:hover {
304
+ background: #4a4a4a;
305
+ }
306
+
307
+ /* Assurez-vous que les éléments de formulaire et les boutons s'affichent correctement */
308
+ .gr-form input, .gr-form textarea, .gr-form select {
309
+ background-color: #2d2d2d !important;
310
+ color: #ffffff !important;
311
+ border: 1px solid #404040 !important;
312
+ }
313
+
314
+ .gr-form label {
315
+ color: #ffffff !important;
316
+ }
317
+
318
+ .gr-form button {
319
+ background-color: #2d2d2d !important;
320
+ color: #ffffff !important;
321
+ border: 1px solid #404040 !important;
322
+ transition: all 0.3s ease !important;
323
+ }
324
+
325
+ .gr-form button:hover {
326
+ background-color: #404040 !important;
327
+ transform: translateY(-2px) !important;
328
+ }
329
+ """
330
+
331
+ with gr.Blocks(theme=gr.themes.Default(), css=css) as demo:
332
+ gr.Markdown(
333
+ """
334
+ # 🎯 Générateur de Présentations PowerPoint IA
335
+
336
+ Créez des présentations professionnelles automatiquement avec l'aide de l'IA.
337
+ """
338
+ )
339
+
340
+ with gr.Row():
341
+ with gr.Column(scale=1):
342
+ text_model_choice = gr.Dropdown(
343
+ choices=list(TEXT_MODELS.keys()),
344
+ value=list(TEXT_MODELS.keys())[0],
345
+ label="Modèle de génération de texte"
346
+ )
347
+ image_model_choice = gr.Dropdown(
348
+ choices=list(IMAGE_MODELS.keys()),
349
+ value=list(IMAGE_MODELS.keys())[0],
350
+ label="Modèle de génération d'images"
351
+ )
352
+ temperature = gr.Slider(
353
+ minimum=0.1,
354
+ maximum=1.0,
355
+ value=0.7,
356
+ step=0.1,
357
+ label="Température"
358
+ )
359
+ max_tokens = gr.Slider(
360
+ minimum=1000,
361
+ maximum=4096,
362
+ value=2048,
363
+ step=256,
364
+ label="Tokens maximum"
365
+ )
366
+ negative_prompt = gr.Textbox(
367
+ lines=2,
368
+ label="Prompt négatif pour les images",
369
+ placeholder="Ce que vous ne voulez pas voir dans les images..."
370
+ )
371
+
372
+ with gr.Row():
373
+ with gr.Column(scale=2):
374
+ input_text = gr.Textbox(
375
+ lines=10,
376
+ label="Votre texte",
377
+ placeholder="Décrivez le contenu que vous souhaitez pour votre présentation..."
378
+ )
379
+ # Modification du composant File pour supprimer l'argument multiple
380
+ file_upload = gr.File(
381
+ label="Documents de référence (PDF, Images)",
382
+ file_types=["pdf", "png", "jpg", "jpeg"]
383
+ )
384
+
385
+ with gr.Row():
386
+ generate_btn = gr.Button("🚀 Générer la présentation", variant="primary")
387
+
388
+ with gr.Row():
389
+ with gr.Column():
390
+ status_output = gr.Textbox(
391
+ label="Statut",
392
+ lines=2
393
+ )
394
+ generated_content = gr.Textbox(
395
+ label="Contenu généré",
396
+ lines=10,
397
+ show_copy_button=True
398
+ )
399
+ output_file = gr.File(
400
+ label="Présentation PowerPoint"
401
+ )
402
+
403
+ generate_btn.click(
404
+ fn=generate_presentation_with_progress,
405
+ inputs=[
406
+ input_text,
407
+ text_model_choice,
408
+ image_model_choice,
409
+ temperature,
410
+ max_tokens,
411
+ negative_prompt
412
+ ],
413
+ outputs=[
414
+ status_output,
415
+ generated_content,
416
+ output_file
417
+ ]
418
+ )
419
+
420
+ if __name__ == "__main__":
421
+ demo.launch()