JairoCesar commited on
Commit
91595ae
·
verified ·
1 Parent(s): 819cf24

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +55 -41
app.py CHANGED
@@ -1,7 +1,7 @@
1
  import streamlit as st
2
  from pptx import Presentation
3
  from pptx.util import Inches, Pt
4
- from pptx.enum.text import MSO_ANCHOR, MSO_AUTO_SIZE, PP_ALIGN # IMPORTACIÓN CORREGIDA AQUÍ
5
  import requests
6
  from PIL import Image
7
  from io import BytesIO
@@ -154,47 +154,51 @@ def generate_presentation_content(topic, client, max_retries=3):
154
  return None
155
 
156
  def generate_conclusion_slides_from_text(text_to_summarize, num_conclusion_slides, topic, client, max_retries=3):
 
157
  if not text_to_summarize: return []
158
  max_len = 30000
159
  if len(text_to_summarize) > max_len:
160
- st.warning(f"Texto del PDF ({len(text_to_summarize)} chars) truncado a {max_len} para conclusiones.")
161
  text_to_summarize = text_to_summarize[:max_len]
162
 
 
163
  prompt = f"""Dado el siguiente texto (extraído de un documento relacionado con el tema general "{topic}"):
164
  --- TEXTO DEL DOCUMENTO ---
165
  {text_to_summarize}
166
  --- FIN DEL TEXTO ---
167
 
168
- Genera {num_conclusion_slides} diapositiva(s) de CONCLUSIÓN para una presentación sobre "{topic}".
169
- Estas diapositivas deben sintetizar las ideas clave del texto proporcionado y conectarlas como conclusiones relevantes para el tema "{topic}".
170
- Los títulos de estas diapositivas deben sonar como conclusiones o puntos finales (ej: "Reflexiones Finales", "Mirando Hacia Adelante", "Conclusiones Clave", "Implicaciones y Futuro"). NO uses la palabra "PDF" ni "Resumen" en los títulos.
171
 
172
  Es CRUCIAL que tu respuesta sea ÚNICAMENTE un JSON válido con la siguiente estructura exacta, sin texto adicional antes o después:
173
  {{
174
  "slides": [
175
- {{"title": "Título Conclusión 1", "content": "Contenido conclusión 1..."}},
176
  // ... (hasta num_conclusion_slides)
177
- {{"title": "Título Conclusión {num_conclusion_slides}", "content": "Contenido conclusión {num_conclusion_slides}..."}}
178
  ]
179
  }}
180
  Debe haber exactamente {num_conclusion_slides} objetos en la lista "slides". Contenido conciso.
181
  Usa comillas dobles para claves y strings.
182
  """
 
 
183
  last_error = None; last_response_text = ""
184
  for attempt in range(max_retries):
185
  try:
186
  response_obj = client.generate_content(prompt)
187
- if not response_obj.parts: raise ValueError("Respuesta del modelo vacía (conclusiones).")
188
  last_response_text = response_obj.text
189
  return parse_gemini_response_for_slides(last_response_text, expected_num_slides=num_conclusion_slides)
190
  except (ValueError, json.JSONDecodeError) as e:
191
- last_error = e; st.warning(f"Intento {attempt + 1}/{max_retries} (conclusiones) fallido: {e}")
192
  if attempt == max_retries - 1:
193
- st.error(f"Error final (conclusiones): {last_error}")
194
- st.text_area("Última resp. (conclusiones):", last_response_text, height=150); return []
195
  except Exception as e:
196
- last_error = e; st.warning(f"Error API Gemini (conclusiones) intento {attempt + 1}: {e}")
197
- if attempt == max_retries - 1: st.error(f"Error API Gemini (conclusiones): {last_error}"); return []
198
  return []
199
 
200
 
@@ -328,13 +332,29 @@ def create_powerpoint(title_slide_content, main_slides_content, template_path, c
328
  if len(prs.slide_layouts) > 5:
329
  title_layout_index = 5
330
  elif not prs.slide_layouts:
331
- prs.slide_master.slide_layouts.add_slide_layout()
 
 
332
  title_layout_index = 0
333
 
334
  if title_layout_index >= len(prs.slide_layouts) :
335
- title_layout_index = 0
336
 
337
- title_layout = prs.slide_layouts[title_layout_index] if prs.slide_layouts else prs.slide_layouts[0] # Fallback final
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
 
339
  slide = prs.slides.add_slide(title_layout)
340
  add_slide_content_and_image(slide, title_slide_content[0], slide_width_emu, slide_height_emu, add_image=False, is_title_slide=True)
@@ -365,7 +385,7 @@ def create_powerpoint(title_slide_content, main_slides_content, template_path, c
365
  def main():
366
  st.set_page_config(page_title="PowerPoint Mágico", layout="wide", initial_sidebar_state="expanded")
367
  st.title("✨ PowerPoint Mágico con el Búho 🦉")
368
- st.markdown("Genera presentaciones impactantes con IA, incluyendo conclusiones de PDF.")
369
 
370
  if not GEMINI_API_KEY or not PIXABAY_API_KEY: return
371
  client = get_gemini_model()
@@ -385,20 +405,13 @@ def main():
385
  if not available_templates:
386
  st.sidebar.warning(f"No hay plantillas .pptx en '{plantillas_dir}'.")
387
  default_prs = Presentation()
388
- # Asegurar que hay al menos un layout usable
389
  if not default_prs.slide_layouts:
390
- try:
391
- default_prs.slide_master.slide_layouts.add_slide_layout() # Intenta añadir un layout maestro
392
- except Exception: # Si falla, al menos intenta con los layouts por defecto
393
- pass
394
  if default_prs.slide_layouts:
395
- try:
396
- default_prs.slides.add_slide(default_prs.slide_layouts[0])
397
- except IndexError: # Si el layout 0 no existe tras añadir
398
- if default_prs.slide_layouts: # Intenta con el primero que encuentre
399
- default_prs.slides.add_slide(default_prs.slide_layouts[0])
400
-
401
-
402
  default_template_path = os.path.join(plantillas_dir, "default.pptx")
403
  try: default_prs.save(default_template_path); st.sidebar.info(f"Plantilla 'default.pptx' creada."); available_templates.append("default.pptx")
404
  except Exception as e: st.sidebar.error(f"No se pudo crear plantilla por defecto: {e}")
@@ -408,11 +421,11 @@ def main():
408
  selected_template_name = st.sidebar.selectbox("🎨 Seleccione una plantilla:", list(template_options.keys()))
409
 
410
 
411
- st.sidebar.subheader("Conclusiones desde PDF (Opcional)")
412
- uploaded_pdf = st.sidebar.file_uploader("📄 Cargue un PDF para generar conclusiones:", type="pdf")
413
- num_conclusion_slides = 0
414
  if uploaded_pdf:
415
- num_conclusion_slides = st.sidebar.number_input("Nº de diapositivas para las conclusiones del PDF:", min_value=1, max_value=3, value=1, step=1)
416
 
417
  if st.button("🚀 Generar Presentación", type="primary", use_container_width=True):
418
  if not topic: st.warning("⚠️ Ingrese un tema."); return
@@ -423,7 +436,7 @@ def main():
423
 
424
  title_slide_content = None
425
  main_slides_content = []
426
- conclusion_slides_content = []
427
 
428
  overall_progress = st.progress(0)
429
  status_text = st.empty()
@@ -442,20 +455,21 @@ def main():
442
  status_text.error("❌ Falló la generación del contenido principal."); overall_progress.progress(100); return
443
  status_text.success("👍 Contenido principal generado."); overall_progress.progress(50)
444
 
445
- if uploaded_pdf and num_conclusion_slides > 0:
446
- status_text.info(f"📄 Procesando PDF '{uploaded_pdf.name}' para conclusiones...")
447
  pdf_text = extract_text_from_pdf(uploaded_pdf); overall_progress.progress(60)
448
  if pdf_text:
449
- status_text.info("✍️ Generando diapositivas de conclusión con Gemini...")
450
- conclusion_slides_content = generate_conclusion_slides_from_text(pdf_text, num_conclusion_slides, topic, client)
451
- if conclusion_slides_content: status_text.success(f"👍 Conclusiones generadas ({len(conclusion_slides_content)} diap.).")
452
- else: status_text.warning("⚠️ No se pudo generar conclusiones del PDF.")
 
453
  else: status_text.warning("⚠️ No se pudo extraer texto del PDF.")
454
  overall_progress.progress(80)
455
 
456
  status_text.info("🛠️ Creando archivo PowerPoint...")
457
  try:
458
- pptx_buffer = create_powerpoint(title_slide_content, main_slides_content, template_path, conclusion_slides_content)
459
  overall_progress.progress(100)
460
  status_text.success("🎉 ¡Presentación generada con éxito!")
461
  clean_topic = re.sub(r'[^\w\s-]', '', topic).strip().replace(' ', '_')
 
1
  import streamlit as st
2
  from pptx import Presentation
3
  from pptx.util import Inches, Pt
4
+ from pptx.enum.text import MSO_ANCHOR, MSO_AUTO_SIZE, PP_ALIGN
5
  import requests
6
  from PIL import Image
7
  from io import BytesIO
 
154
  return None
155
 
156
  def generate_conclusion_slides_from_text(text_to_summarize, num_conclusion_slides, topic, client, max_retries=3):
157
+ """Genera diapositivas de 'conclusión' (contenido relevante del PDF) basadas en el texto y el tema general."""
158
  if not text_to_summarize: return []
159
  max_len = 30000
160
  if len(text_to_summarize) > max_len:
161
+ st.warning(f"Texto del PDF ({len(text_to_summarize)} chars) truncado a {max_len} para las diapositivas adicionales.")
162
  text_to_summarize = text_to_summarize[:max_len]
163
 
164
+ # ***** INICIO DE LA MODIFICACIÓN DEL PROMPT *****
165
  prompt = f"""Dado el siguiente texto (extraído de un documento relacionado con el tema general "{topic}"):
166
  --- TEXTO DEL DOCUMENTO ---
167
  {text_to_summarize}
168
  --- FIN DEL TEXTO ---
169
 
170
+ Genera {num_conclusion_slides} diapositiva(s) de contenido relevante para una presentación sobre "{topic}", basándote en el texto anterior.
171
+ Estas diapositivas deben sintetizar las ideas clave del texto proporcionado y presentarlas de forma que complementen el tema "{topic}".
172
+ Los títulos de estas diapositivas deben ser descriptivos del contenido de cada una y mantener coherencia con el tema general "{topic}". NO uses la palabra "PDF", "Resumen" ni frases explícitas como "Conclusiones", "Reflexiones Finales", etc., en los títulos. Simplemente titula la diapositiva según su contenido principal extraído del documento.
173
 
174
  Es CRUCIAL que tu respuesta sea ÚNICAMENTE un JSON válido con la siguiente estructura exacta, sin texto adicional antes o después:
175
  {{
176
  "slides": [
177
+ {{"title": "Título Descriptivo Diapositiva 1 (basado en contenido)", "content": "Contenido de la diapositiva 1..."}},
178
  // ... (hasta num_conclusion_slides)
179
+ {{"title": "Título Descriptivo Diapositiva {num_conclusion_slides} (basado en contenido)", "content": "Contenido de la diapositiva {num_conclusion_slides}..."}}
180
  ]
181
  }}
182
  Debe haber exactamente {num_conclusion_slides} objetos en la lista "slides". Contenido conciso.
183
  Usa comillas dobles para claves y strings.
184
  """
185
+ # ***** FIN DE LA MODIFICACIÓN DEL PROMPT *****
186
+
187
  last_error = None; last_response_text = ""
188
  for attempt in range(max_retries):
189
  try:
190
  response_obj = client.generate_content(prompt)
191
+ if not response_obj.parts: raise ValueError("Respuesta del modelo vacía (diapositivas adicionales PDF).")
192
  last_response_text = response_obj.text
193
  return parse_gemini_response_for_slides(last_response_text, expected_num_slides=num_conclusion_slides)
194
  except (ValueError, json.JSONDecodeError) as e:
195
+ last_error = e; st.warning(f"Intento {attempt + 1}/{max_retries} (diapositivas adicionales PDF) fallido: {e}")
196
  if attempt == max_retries - 1:
197
+ st.error(f"Error final (diapositivas adicionales PDF): {last_error}")
198
+ st.text_area("Última resp. (diapositivas adicionales PDF):", last_response_text, height=150); return []
199
  except Exception as e:
200
+ last_error = e; st.warning(f"Error API Gemini (diapositivas adicionales PDF) intento {attempt + 1}: {e}")
201
+ if attempt == max_retries - 1: st.error(f"Error API Gemini (diapositivas adicionales PDF): {last_error}"); return []
202
  return []
203
 
204
 
 
332
  if len(prs.slide_layouts) > 5:
333
  title_layout_index = 5
334
  elif not prs.slide_layouts:
335
+ try:
336
+ prs.slide_master.slide_layouts.add_slide_layout()
337
+ except Exception: pass # Si falla, los siguientes chequeos deberían manejarlo
338
  title_layout_index = 0
339
 
340
  if title_layout_index >= len(prs.slide_layouts) :
341
+ title_layout_index = 0 if prs.slide_layouts else -1 # -1 si no hay layouts en absoluto
342
 
343
+ if title_layout_index == -1 or not prs.slide_layouts: # No hay layouts o el índice es inválido
344
+ # Como último recurso, intenta usar el layout 1 si existe, o crea uno si es posible
345
+ # Esto es para plantillas extremadamente vacías o corruptas
346
+ if len(prs.slide_layouts) > 1:
347
+ title_layout = prs.slide_layouts[1]
348
+ else: # Intenta añadir uno y usarlo, o fallará si es imposible
349
+ try:
350
+ title_layout = prs.slide_master.slide_layouts.add_slide_layout()
351
+ if not title_layout: # Si add_slide_layout retorna None o falla silenciosamente
352
+ raise Exception("No se pudo obtener o crear un slide layout.")
353
+ except Exception as e_layout:
354
+ st.error(f"Error crítico: No se pudieron obtener layouts de la plantilla: {e_layout}")
355
+ raise # Re-lanza la excepción para detener la creación de PPT
356
+ else:
357
+ title_layout = prs.slide_layouts[title_layout_index]
358
 
359
  slide = prs.slides.add_slide(title_layout)
360
  add_slide_content_and_image(slide, title_slide_content[0], slide_width_emu, slide_height_emu, add_image=False, is_title_slide=True)
 
385
  def main():
386
  st.set_page_config(page_title="PowerPoint Mágico", layout="wide", initial_sidebar_state="expanded")
387
  st.title("✨ PowerPoint Mágico con el Búho 🦉")
388
+ st.markdown("Genera presentaciones impactantes con IA, incluyendo contenido adicional de PDF.")
389
 
390
  if not GEMINI_API_KEY or not PIXABAY_API_KEY: return
391
  client = get_gemini_model()
 
405
  if not available_templates:
406
  st.sidebar.warning(f"No hay plantillas .pptx en '{plantillas_dir}'.")
407
  default_prs = Presentation()
 
408
  if not default_prs.slide_layouts:
409
+ try: default_prs.slide_master.slide_layouts.add_slide_layout()
410
+ except Exception: pass
 
 
411
  if default_prs.slide_layouts:
412
+ try: default_prs.slides.add_slide(default_prs.slide_layouts[0])
413
+ except IndexError:
414
+ if default_prs.slide_layouts: default_prs.slides.add_slide(default_prs.slide_layouts[0])
 
 
 
 
415
  default_template_path = os.path.join(plantillas_dir, "default.pptx")
416
  try: default_prs.save(default_template_path); st.sidebar.info(f"Plantilla 'default.pptx' creada."); available_templates.append("default.pptx")
417
  except Exception as e: st.sidebar.error(f"No se pudo crear plantilla por defecto: {e}")
 
421
  selected_template_name = st.sidebar.selectbox("🎨 Seleccione una plantilla:", list(template_options.keys()))
422
 
423
 
424
+ st.sidebar.subheader("Contenido Adicional desde PDF (Opcional)")
425
+ uploaded_pdf = st.sidebar.file_uploader("📄 Cargue un PDF para generar diapositivas adicionales:", type="pdf")
426
+ num_additional_slides = 0 # Renombrado de num_conclusion_slides
427
  if uploaded_pdf:
428
+ num_additional_slides = st.sidebar.number_input("Nº de diapositivas adicionales desde el PDF:", min_value=1, max_value=3, value=1, step=1)
429
 
430
  if st.button("🚀 Generar Presentación", type="primary", use_container_width=True):
431
  if not topic: st.warning("⚠️ Ingrese un tema."); return
 
436
 
437
  title_slide_content = None
438
  main_slides_content = []
439
+ additional_slides_content = [] # Renombrado de conclusion_slides_content
440
 
441
  overall_progress = st.progress(0)
442
  status_text = st.empty()
 
455
  status_text.error("❌ Falló la generación del contenido principal."); overall_progress.progress(100); return
456
  status_text.success("👍 Contenido principal generado."); overall_progress.progress(50)
457
 
458
+ if uploaded_pdf and num_additional_slides > 0:
459
+ status_text.info(f"📄 Procesando PDF '{uploaded_pdf.name}' para contenido adicional...")
460
  pdf_text = extract_text_from_pdf(uploaded_pdf); overall_progress.progress(60)
461
  if pdf_text:
462
+ status_text.info("✍️ Generando diapositivas adicionales desde el PDF con Gemini...")
463
+ # Usar la función renombrada o la original con el prompt modificado
464
+ additional_slides_content = generate_conclusion_slides_from_text(pdf_text, num_additional_slides, topic, client)
465
+ if additional_slides_content: status_text.success(f"👍 Contenido adicional del PDF generado ({len(additional_slides_content)} diap.).")
466
+ else: status_text.warning("⚠️ No se pudo generar contenido adicional del PDF.")
467
  else: status_text.warning("⚠️ No se pudo extraer texto del PDF.")
468
  overall_progress.progress(80)
469
 
470
  status_text.info("🛠️ Creando archivo PowerPoint...")
471
  try:
472
+ pptx_buffer = create_powerpoint(title_slide_content, main_slides_content, template_path, additional_slides_content)
473
  overall_progress.progress(100)
474
  status_text.success("🎉 ¡Presentación generada con éxito!")
475
  clean_topic = re.sub(r'[^\w\s-]', '', topic).strip().replace(' ', '_')