YashMK89 commited on
Commit
82688e5
Β·
verified Β·
1 Parent(s): fa1fb11

Update pages/1_πŸ“Š_Text_to_PPT.py

Browse files
Files changed (1) hide show
  1. pages/1_πŸ“Š_Text_to_PPT.py +163 -491
pages/1_πŸ“Š_Text_to_PPT.py CHANGED
@@ -1010,6 +1010,7 @@
1010
 
1011
 
1012
  ###################################################################################################################################################################
 
1013
  import streamlit as st
1014
  import google.generativeai as genai
1015
  from pptx import Presentation
@@ -1017,16 +1018,11 @@ from pptx.util import Inches, Pt
1017
  from pptx.dml.color import RGBColor
1018
  from pptx.enum.text import PP_ALIGN, PP_PARAGRAPH_ALIGNMENT
1019
  from pptx.enum.text import MSO_AUTO_SIZE
1020
- from pptx.enum.shapes import MSO_SHAPE
1021
  from pptx.oxml.xmlchemy import OxmlElement
1022
  import io
1023
  import re
1024
  import tempfile
1025
  import os
1026
- from PIL import Image
1027
- import time
1028
- import textwrap
1029
- import math
1030
 
1031
  # Load the API key securely from environment variable
1032
  api_key = os.getenv("GOOGLE_API_KEY")
@@ -1043,8 +1039,7 @@ DEFAULT_THEMES = {
1043
  "text_color": RGBColor(200, 200, 200),
1044
  "accent": RGBColor(0, 112, 192),
1045
  "title_font": "Calibri",
1046
- "text_font": "Calibri",
1047
- "min_font_size": 14
1048
  },
1049
  "Modern Green": {
1050
  "background": RGBColor(22, 82, 66),
@@ -1052,8 +1047,7 @@ DEFAULT_THEMES = {
1052
  "text_color": RGBColor(220, 220, 220),
1053
  "accent": RGBColor(76, 175, 80),
1054
  "title_font": "Arial",
1055
- "text_font": "Arial",
1056
- "min_font_size": 14
1057
  },
1058
  "Light Corporate": {
1059
  "background": RGBColor(255, 255, 255),
@@ -1061,8 +1055,7 @@ DEFAULT_THEMES = {
1061
  "text_color": RGBColor(33, 33, 33),
1062
  "accent": RGBColor(25, 118, 210),
1063
  "title_font": "Segoe UI",
1064
- "text_font": "Segoe UI",
1065
- "min_font_size": 14
1066
  },
1067
  "Dark Tech": {
1068
  "background": RGBColor(33, 33, 33),
@@ -1070,18 +1063,10 @@ DEFAULT_THEMES = {
1070
  "text_color": RGBColor(200, 200, 200),
1071
  "accent": RGBColor(0, 150, 255),
1072
  "title_font": "Consolas",
1073
- "text_font": "Consolas",
1074
- "min_font_size": 14
1075
  }
1076
  }
1077
 
1078
- # PowerPoint compatibility options
1079
- PPT_VERSIONS = {
1080
- "Modern (2013+)": {"aspect_ratio": (13.33, 7.5)},
1081
- "Standard (2007-2013)": {"aspect_ratio": (10, 7.5)},
1082
- "Legacy (2003)": {"aspect_ratio": (10, 7.5)}
1083
- }
1084
-
1085
  def hex_to_rgb(hex_color):
1086
  """Convert hex color to RGBColor"""
1087
  hex_color = hex_color.lstrip('#')
@@ -1101,7 +1086,6 @@ def extract_theme_from_pptx(uploaded_file):
1101
  "accent": RGBColor(79, 129, 189), # Default blue
1102
  "title_font": "Calibri",
1103
  "text_font": "Calibri",
1104
- "min_font_size": 14,
1105
  "template_path": tmp_file_path # Store the template path for later use
1106
  }
1107
 
@@ -1142,12 +1126,8 @@ def extract_theme_from_pptx(uploaded_file):
1142
 
1143
  return theme
1144
 
1145
- @st.cache_data(ttl=3600, show_spinner="Generating slide content...")
1146
- def generate_slide_content(topic, slide_count, _model=None):
1147
- """Generate slide content with caching"""
1148
- if _model is None:
1149
- _model = genai.GenerativeModel('gemini-2.0-flash')
1150
-
1151
  prompt = f"""Create a comprehensive presentation on '{topic}' with exactly {slide_count} slides.
1152
  For each slide, provide:
1153
  1. A clear title in [Title:] format
@@ -1218,7 +1198,7 @@ def generate_slide_content(topic, slide_count, _model=None):
1218
  Begin the content generation now.
1219
  """
1220
 
1221
- response = _model.generate_content(prompt)
1222
  return response.text
1223
 
1224
  def parse_slide_content(slide_text):
@@ -1315,212 +1295,100 @@ def parse_slide_content(slide_text):
1315
 
1316
  return slides, questionnaire, answer_key
1317
 
1318
- def add_title_separator(slide, title_shape, accent_color):
1319
- """Add a decorative line under the title"""
1320
- line = slide.shapes.add_shape(
1321
- MSO_SHAPE.RECTANGLE,
1322
- title_shape.left,
1323
- title_shape.top + title_shape.height - Pt(5),
1324
- title_shape.width,
1325
- Pt(3)
1326
- )
1327
- fill = line.fill
1328
- fill.solid()
1329
- fill.fore_color.rgb = accent_color
1330
- line.line.fill.background()
1331
-
1332
- def smart_text_wrap(text, max_width=100):
1333
- """Wrap text intelligently at sentence boundaries when possible"""
1334
- if len(text) <= max_width:
1335
- return [text]
1336
-
1337
- # Try to split at sentence boundaries
1338
- sentences = re.split(r'(?<=[.!?]) +', text)
1339
- lines = []
1340
- current_line = ""
1341
-
1342
- for sentence in sentences:
1343
- if len(current_line) + len(sentence) + 1 <= max_width:
1344
- current_line += (sentence + " ") if current_line else sentence
1345
- else:
1346
- if current_line:
1347
- lines.append(current_line.strip())
1348
- current_line = sentence
1349
-
1350
- if current_line:
1351
- lines.append(current_line.strip())
1352
-
1353
- # If we didn't split into sentences, do regular word wrap
1354
- if len(lines) == 1 and len(text) > max_width:
1355
- return textwrap.wrap(text, max_width)
1356
-
1357
- return lines
1358
-
1359
- def calculate_optimal_font_size(text, max_width, max_height, initial_size=18):
1360
- """Calculate optimal font size to fit text in specified dimensions"""
1361
- # Estimate based on character count
1362
- char_count = len(text)
1363
- if char_count < 100:
1364
- return initial_size
1365
- elif char_count < 200:
1366
- return max(initial_size - 2, 14)
1367
- elif char_count < 300:
1368
- return max(initial_size - 4, 12)
1369
- else:
1370
- return max(initial_size - 6, 10)
1371
-
1372
  def create_question_slide(prs, question, question_num, theme):
1373
- """Create a slide for a single question with proper text wrapping"""
1374
- # Try to use Title + Content layout, fallback to blank if not available
1375
- try:
1376
- slide = prs.slides.add_slide(prs.slide_layouts[1]) # Title + Content layout
1377
- except IndexError:
1378
- slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank layout
1379
-
1380
- # Apply background if using custom theme
1381
- if "template_path" not in theme:
1382
- background = slide.background
1383
- fill = background.fill
1384
- fill.solid()
1385
- fill.fore_color.rgb = theme["background"]
1386
-
1387
- # Remove unused placeholders
1388
- for shape in slide.shapes:
1389
- if shape.has_text_frame and not shape.text.strip():
1390
- sp = shape._element
1391
- sp.getparent().remove(sp)
1392
 
1393
  # Set title
1394
- title = slide.shapes.title if hasattr(slide.shapes, 'title') else None
1395
- if title:
1396
- title.text = f"Question {question_num}"
1397
- if "template_path" not in theme:
1398
- title.text_frame.paragraphs[0].font.color.rgb = theme["accent"]
1399
- title.text_frame.paragraphs[0].font.size = Pt(36)
1400
- title.text_frame.paragraphs[0].font.bold = True
1401
- if "title_font" in theme:
1402
- title.text_frame.paragraphs[0].font.name = theme["title_font"]
1403
-
1404
- # Add decorative line under title
1405
- add_title_separator(slide, title, theme["accent"])
1406
-
1407
- # Create content area with proper spacing
1408
- left = Inches(1)
1409
- top = Inches(2) if title else Inches(0.5)
1410
- width = prs.slide_width - Inches(2)
1411
- height = prs.slide_height - top - Inches(1) # Leave 1 inch at bottom
1412
-
1413
- content_box = slide.shapes.add_textbox(left, top, width, height)
1414
- tf = content_box.text_frame
1415
- tf.word_wrap = True
1416
 
1417
- # Add question text with smart wrapping
1418
- question_text = question['text']
1419
- wrapped_question = smart_text_wrap(question_text, 100)
1420
-
1421
- # Calculate optimal font size
1422
- font_size = calculate_optimal_font_size(question_text, width, height, 24)
1423
-
1424
- for line in wrapped_question:
1425
- p = tf.add_paragraph()
1426
- p.text = line
1427
- p.font.bold = True
1428
- p.space_after = Pt(12)
1429
-
1430
- # Apply text formatting
1431
- if "template_path" not in theme:
1432
- p.font.color.rgb = theme["text_color"]
1433
- p.font.size = Pt(font_size)
1434
- if "text_font" in theme:
1435
- p.font.name = theme["text_font"]
 
 
 
 
 
 
 
1436
 
1437
- # Add options with proper spacing
1438
  for j, opt in enumerate(question['options']):
1439
  p = tf.add_paragraph()
1440
  p.text = f"{chr(65+j)}. {opt}"
1441
  p.level = 0
1442
- p.space_before = Pt(12)
1443
  p.space_after = Pt(12)
1444
 
1445
- # Apply text formatting
1446
  if "template_path" not in theme:
1447
  p.font.color.rgb = theme["text_color"]
1448
- p.font.size = Pt(font_size - 4) # Slightly smaller for options
1449
  if "text_font" in theme:
1450
  p.font.name = theme["text_font"]
1451
 
1452
  # Add correct answer to speaker notes
1453
- if hasattr(slide, 'notes_slide'):
1454
- notes_slide = slide.notes_slide
1455
- notes_text = f"Correct Answer: {question['correct']}\n"
1456
- if hasattr(notes_slide, 'notes_text_frame'):
1457
- notes_slide.notes_text_frame.text = notes_text
1458
 
1459
  return slide
1460
 
1461
  def create_answer_key_slide(prs, questions, theme):
1462
- """Create answer key slide with proper text wrapping"""
1463
- # Try to use Title + Content layout, fallback to blank if not available
1464
- try:
1465
- slide = prs.slides.add_slide(prs.slide_layouts[1]) # Title + Content layout
1466
- except IndexError:
1467
- slide = prs.slides.add_slide(prs.slide_layouts[6]) # Blank layout
1468
-
1469
- # Apply background if using custom theme
1470
- if "template_path" not in theme:
1471
- background = slide.background
1472
- fill = background.fill
1473
- fill.solid()
1474
- fill.fore_color.rgb = theme["background"]
1475
-
1476
- # Remove unused placeholders
1477
- for shape in slide.shapes:
1478
- if shape.has_text_frame and not shape.text.strip():
1479
- sp = shape._element
1480
- sp.getparent().remove(sp)
1481
 
1482
  # Set title
1483
- title = slide.shapes.title if hasattr(slide.shapes, 'title') else None
1484
- if title:
1485
- title.text = "Answer Key"
1486
- if "template_path" not in theme:
1487
- title.text_frame.paragraphs[0].font.color.rgb = theme["accent"]
1488
- title.text_frame.paragraphs[0].font.size = Pt(36)
1489
- title.text_frame.paragraphs[0].font.bold = True
1490
- if "title_font" in theme:
1491
- title.text_frame.paragraphs[0].font.name = theme["title_font"]
1492
-
1493
- # Add decorative line under title
1494
- add_title_separator(slide, title, theme["accent"])
1495
 
1496
- # Create content area with proper spacing
1497
- left = Inches(1.5)
1498
- top = Inches(2) if title else Inches(0.5)
1499
- width = prs.slide_width - Inches(3)
1500
- height = prs.slide_height - top - Inches(1) # Leave 1 inch at bottom
 
 
1501
 
1502
- content_box = slide.shapes.add_textbox(left, top, width, height)
1503
- tf = content_box.text_frame
1504
- tf.word_wrap = True
 
1505
 
1506
  # Add answer key
1507
  for i, q in enumerate(questions):
1508
  p = tf.add_paragraph()
1509
  p.text = f"Question {i+1}: {q['correct']}"
1510
  p.level = 0
1511
- p.space_after = Pt(18)
1512
 
1513
- # Apply text formatting
1514
  if "template_path" not in theme:
1515
  p.font.color.rgb = theme["text_color"]
1516
  p.font.size = Pt(24)
1517
- p.font.bold = True
1518
  if "text_font" in theme:
1519
  p.font.name = theme["text_font"]
1520
 
1521
  return slide
1522
 
1523
- def create_detailed_pptx(slides_data, questions, theme, branding_options=None, ppt_version="Modern (2013+)"):
1524
  """Create PowerPoint using the uploaded template"""
1525
  # Use the template if one was uploaded
1526
  if "template_path" in theme and os.path.exists(theme["template_path"]):
@@ -1528,10 +1396,9 @@ def create_detailed_pptx(slides_data, questions, theme, branding_options=None, p
1528
  else:
1529
  prs = Presentation()
1530
 
1531
- # Set aspect ratio based on selected version
1532
- aspect_ratio = PPT_VERSIONS[ppt_version]["aspect_ratio"]
1533
- prs.slide_width = Inches(aspect_ratio[0])
1534
- prs.slide_height = Inches(aspect_ratio[1])
1535
 
1536
  # Layout mapping
1537
  layout_indices = {
@@ -1549,7 +1416,7 @@ def create_detailed_pptx(slides_data, questions, theme, branding_options=None, p
1549
  default_layout_idx = available_layouts.get('title-content', 0)
1550
 
1551
  # Create main slides
1552
- for i, slide_info in enumerate(slides_data, 1):
1553
  layout = slide_info.get('layout', 'title-content').lower()
1554
  layout_idx = available_layouts.get(layout, default_layout_idx)
1555
 
@@ -1558,153 +1425,87 @@ def create_detailed_pptx(slides_data, questions, theme, branding_options=None, p
1558
  except IndexError:
1559
  slide = prs.slides.add_slide(prs.slide_layouts[default_layout_idx])
1560
 
1561
- # Apply background if using custom theme
 
 
 
 
 
1562
  if "template_path" not in theme:
 
1563
  background = slide.background
1564
  fill = background.fill
1565
  fill.solid()
1566
  fill.fore_color.rgb = theme["background"]
1567
-
1568
- # Remove unused placeholders
1569
- for shape in slide.shapes:
1570
- if shape.has_text_frame and not shape.text.strip():
1571
- sp = shape._element
1572
- sp.getparent().remove(sp)
1573
-
1574
- # Try to get the title placeholder
1575
- title_placeholder = slide.shapes.title if hasattr(slide.shapes, 'title') else None
1576
-
1577
- # Set title if placeholder exists
1578
- if title_placeholder is not None:
1579
- title_placeholder.text = slide_info['title']
1580
- title_placeholder.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
1581
 
1582
- # Only apply custom formatting if not using a template
1583
- if "template_path" not in theme:
1584
- # Format title
1585
- title_placeholder.text_frame.paragraphs[0].font.color.rgb = theme["title_color"]
1586
- title_placeholder.text_frame.paragraphs[0].font.size = Pt(36)
1587
- title_placeholder.text_frame.paragraphs[0].font.bold = True
1588
- if "title_font" in theme:
1589
- title_placeholder.text_frame.paragraphs[0].font.name = theme["title_font"]
1590
-
1591
- # Add decorative line under title
1592
- add_title_separator(slide, title_placeholder, theme["accent"])
1593
- else:
1594
- # Add title manually if no placeholder
1595
- left = Inches(1)
1596
- top = Inches(0.5)
1597
- width = prs.slide_width - Inches(2)
1598
- height = Inches(1)
1599
- title_box = slide.shapes.add_textbox(left, top, width, height)
1600
- title_tf = title_box.text_frame
1601
- p = title_tf.add_paragraph()
1602
- p.text = slide_info['title']
1603
- p.alignment = PP_ALIGN.CENTER
1604
- p.font.bold = True
1605
- p.font.size = Pt(36)
1606
  if "title_font" in theme:
1607
- p.font.name = theme["title_font"]
1608
- if "title_color" in theme:
1609
- p.font.color.rgb = theme["title_color"]
 
 
 
1610
 
1611
  # Set content based on layout
1612
- if layout == 'two-column':
1613
  content = slide_info.get('content', [])
1614
  mid_point = len(content) // 2
1615
  left_content = content[:mid_point]
1616
  right_content = content[mid_point:]
1617
 
1618
- # Create left column
1619
- left = Inches(0.5)
1620
- top = Inches(1.8) # Increased top margin
1621
- width = prs.slide_width / 2 - Inches(1)
1622
- height = prs.slide_height - top - Inches(1) # Leave space at bottom
1623
- left_box = slide.shapes.add_textbox(left, top, width, height)
1624
- left_tf = left_box.text_frame
1625
 
1626
- # Create right column
1627
- left = prs.slide_width / 2 + Inches(0.5)
1628
- right_box = slide.shapes.add_textbox(left, top, width, height)
1629
- right_tf = right_box.text_frame
1630
 
1631
  for content_part, tf in [(left_content, left_tf), (right_content, right_tf)]:
1632
  for point in content_part:
1633
- # Wrap text to prevent overflow
1634
- wrapped_text = smart_text_wrap(point, 100)
1635
-
1636
- # Calculate optimal font size
1637
- font_size = calculate_optimal_font_size(point, width, height, 18)
1638
-
1639
- for line in wrapped_text:
1640
- p = tf.add_paragraph()
1641
- p.text = line
1642
- p.level = 0
1643
- p.alignment = PP_ALIGN.JUSTIFY
1644
- p.space_after = Pt(8) # Reduced space between lines
1645
- if "template_path" not in theme: # Only apply custom formatting if no template
1646
- p.font.color.rgb = theme["text_color"]
1647
- p.font.size = Pt(font_size)
1648
- if "text_font" in theme:
1649
- p.font.name = theme["text_font"]
1650
-
1651
- elif layout != 'title-only': # Content slides
1652
- # Create content area
1653
- left = Inches(1)
1654
- top = Inches(1.8) # Increased top margin
1655
- width = prs.slide_width - Inches(2)
1656
- height = prs.slide_height - top - Inches(1) # Leave space at bottom
1657
- content_box = slide.shapes.add_textbox(left, top, width, height)
1658
- tf = content_box.text_frame
1659
-
1660
- for point in slide_info.get('content', []):
1661
- # Wrap text to prevent overflow
1662
- wrapped_text = smart_text_wrap(point, 100)
1663
-
1664
- # Calculate optimal font size
1665
- font_size = calculate_optimal_font_size(point, width, height, 18)
1666
-
1667
- for line in wrapped_text:
1668
  p = tf.add_paragraph()
1669
- p.text = line
 
1670
  p.level = 0
1671
- p.alignment = PP_ALIGN.JUSTIFY
1672
- p.space_after = Pt(8) # Reduced space between lines
1673
  if "template_path" not in theme: # Only apply custom formatting if no template
1674
  p.font.color.rgb = theme["text_color"]
1675
- p.font.size = Pt(font_size)
1676
  if "text_font" in theme:
1677
  p.font.name = theme["text_font"]
1678
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1679
  # Add notes if available
1680
- if slide_info.get('notes') and hasattr(slide, 'notes_slide'):
1681
  notes_slide = slide.notes_slide
1682
- if hasattr(notes_slide, 'notes_text_frame'):
1683
- notes_slide.notes_text_frame.text = slide_info['notes']
1684
 
1685
- # Add question slides (one per question) with consistent theme
1686
  if questions:
1687
- # Add section header with consistent theme
1688
- try:
1689
- section_slide = prs.slides.add_slide(prs.slide_layouts[2]) # Section header layout
1690
- except IndexError:
1691
- section_slide = prs.slides.add_slide(prs.slide_layouts[0]) # Fallback to title slide
1692
-
1693
- # Apply background if using custom theme
1694
- if "template_path" not in theme:
1695
- background = section_slide.background
1696
- fill = background.fill
1697
- fill.solid()
1698
- fill.fore_color.rgb = theme["background"]
1699
-
1700
- title_placeholder = section_slide.shapes.title if hasattr(section_slide.shapes, 'title') else None
1701
- if title_placeholder is not None:
1702
- title_placeholder.text = "Knowledge Check"
1703
-
1704
- # Format section header
1705
- if "template_path" not in theme:
1706
- title_placeholder.text_frame.paragraphs[0].font.color.rgb = theme["accent"]
1707
- title_placeholder.text_frame.paragraphs[0].font.size = Pt(44)
1708
 
1709
  # Add each question on a separate slide
1710
  for i, question in enumerate(questions, 1):
@@ -1723,53 +1524,6 @@ def create_detailed_pptx(slides_data, questions, theme, branding_options=None, p
1723
 
1724
  return pptx_io
1725
 
1726
- def regenerate_slide(slide_index, topic, slide_count, theme, ppt_version):
1727
- """Regenerate a specific slide using AI"""
1728
- model = genai.GenerativeModel('gemini-2.0-flash')
1729
- prompt = f"""We are creating a presentation on '{topic}' with {slide_count} slides.
1730
- Please regenerate only slide number {slide_index+1} with the same structure as the others.
1731
- The slide should have:
1732
- - A clear title in [Title:] format
1733
- - 3-5 detailed bullet points in [Content:] format (each point should be concise, 1-2 lines max)
1734
- - Optional speaker notes in [Notes:] format
1735
- - Layout suggestion in [Layout:] format
1736
-
1737
- Format:
1738
- [Title:] Slide Title
1739
- [Layout:] layout-type
1740
- [Content:]
1741
- - Point 1: Concise point
1742
- - Point 2: Another concise point
1743
- [Notes:] Optional notes
1744
-
1745
- Make sure the content is concise and fits on a single slide. Avoid long paragraphs.
1746
- """
1747
-
1748
- response = model.generate_content(prompt)
1749
- new_slide_text = response.text
1750
-
1751
- # Parse the new slide
1752
- new_slide = {'title': '', 'content': [], 'notes': '', 'layout': 'title-content'}
1753
- for line in new_slide_text.split('\n'):
1754
- line = line.strip()
1755
- if line.startswith('[Title:]'):
1756
- new_slide['title'] = line.replace('[Title:]', '').strip()
1757
- elif line.startswith('[Content:]'):
1758
- pass # Content follows on subsequent lines
1759
- elif line.startswith('[Notes:]'):
1760
- new_slide['notes'] = line.replace('[Notes:]', '').strip()
1761
- elif line.startswith('[Layout:]'):
1762
- layout = line.replace('[Layout:]', '').strip().lower()
1763
- valid_layouts = ['title-only', 'title-content', 'two-column', 'section-header']
1764
- new_slide['layout'] = layout if layout in valid_layouts else 'title-content'
1765
- elif line.startswith('-'):
1766
- point = line[1:].strip()
1767
- if ':' in point:
1768
- point = point.split(':')[0].strip()
1769
- new_slide['content'].append(point)
1770
-
1771
- return new_slide
1772
-
1773
  def main():
1774
  st.set_page_config(page_title="Advanced PPTX Generator", layout="wide")
1775
 
@@ -1780,18 +1534,6 @@ def main():
1780
  if 'custom_themes' not in st.session_state:
1781
  st.session_state.custom_themes = {}
1782
 
1783
- # Initialize session state for presentation data
1784
- if 'slides_data' not in st.session_state:
1785
- st.session_state.slides_data = []
1786
- if 'questionnaire' not in st.session_state:
1787
- st.session_state.questionnaire = []
1788
- if 'answer_key' not in st.session_state:
1789
- st.session_state.answer_key = []
1790
- if 'pptx_file' not in st.session_state:
1791
- st.session_state.pptx_file = None
1792
- if 'regenerate_map' not in st.session_state:
1793
- st.session_state.regenerate_map = {}
1794
-
1795
  # Combine default and custom themes
1796
  ALL_THEMES = {**DEFAULT_THEMES, **st.session_state.custom_themes}
1797
  col1, col2 = st.columns([3, 1])
@@ -1826,7 +1568,6 @@ def main():
1826
  accent_color = st.color_picker("Accent Color", "#0070C0")
1827
  title_font = st.text_input("Title Font", "Calibri")
1828
  text_font = st.text_input("Text Font", "Calibri")
1829
- min_font_size = st.slider("Minimum Font Size", 10, 18, 14)
1830
 
1831
  if st.form_submit_button("Save Custom Theme"):
1832
  if new_theme_name:
@@ -1836,8 +1577,7 @@ def main():
1836
  "text_color": hex_to_rgb(text_color),
1837
  "accent": hex_to_rgb(accent_color),
1838
  "title_font": title_font,
1839
- "text_font": text_font,
1840
- "min_font_size": min_font_size
1841
  }
1842
  st.success(f"Theme '{new_theme_name}' saved successfully!")
1843
  else:
@@ -1852,14 +1592,6 @@ def main():
1852
  st.success("Theme extracted from template!")
1853
  else:
1854
  st.info("Please upload a PowerPoint file to extract its theme")
1855
-
1856
- # PowerPoint version compatibility
1857
- ppt_version = st.selectbox(
1858
- "PowerPoint Version Compatibility:",
1859
- list(PPT_VERSIONS.keys()),
1860
- index=0
1861
- )
1862
- st.caption("Modern (2013+): Widescreen (16:9)\nStandard (2007-2013): Standard (4:3)\nLegacy (2003): Standard (4:3)")
1863
 
1864
  with col2:
1865
  st.markdown("### Instructions")
@@ -1867,117 +1599,57 @@ def main():
1867
  st.markdown("2. Select slide count and theme")
1868
  st.markdown("3. Click 'Generate Presentation'")
1869
  st.markdown("4. Download your PowerPoint file")
1870
- st.markdown("5. Use 'Regenerate' buttons to improve specific slides")
1871
 
1872
- generate_btn = st.button("Generate Presentation", type="primary", key="generate_btn")
1873
- if generate_btn:
1874
  if not topic:
1875
  st.warning("Please enter a topic first!")
1876
  elif theme_option == "Example-Based Theme" and not uploaded_file:
1877
  st.warning("Please upload a PowerPoint template file first")
1878
  else:
1879
- with st.spinner("Generating your presentation..."):
1880
- progress_bar = st.progress(0)
1881
- status_text = st.empty()
1882
-
1883
- # Generate slide content
1884
- status_text.text("Generating content with AI...")
1885
- progress_bar.progress(20)
1886
- model = genai.GenerativeModel('gemini-2.0-flash')
1887
- slide_text = generate_slide_content(topic, slide_count, _model=model)
1888
-
1889
- # Parse slide content
1890
- status_text.text("Parsing slide content...")
1891
- progress_bar.progress(40)
1892
- slides_data, questionnaire, answer_key = parse_slide_content(slide_text)
1893
-
1894
- # Store in session state
1895
- st.session_state.slides_data = slides_data
1896
- st.session_state.questionnaire = questionnaire
1897
- st.session_state.answer_key = answer_key
1898
- st.session_state.regenerate_map = {i: False for i in range(len(slides_data))}
1899
-
1900
- # Create PowerPoint
1901
- status_text.text("Creating PowerPoint file...")
1902
- progress_bar.progress(70)
1903
- pptx_file = create_detailed_pptx(slides_data, questionnaire, theme, ppt_version=ppt_version)
1904
- st.session_state.pptx_file = pptx_file
1905
-
1906
- progress_bar.progress(100)
1907
- status_text.text("Done!")
1908
- time.sleep(1)
1909
- progress_bar.empty()
1910
- status_text.empty()
1911
-
1912
- st.success("Presentation generated successfully!")
1913
-
1914
- # Show slide overview with regeneration options
1915
- if st.session_state.slides_data:
1916
- with st.expander("Slide Overview (Click to Expand)", expanded=True):
1917
- st.markdown(f"**Total Slides:** {len(st.session_state.slides_data)} content slides + "
1918
- f"{len(st.session_state.questionnaire)} question slides + 1 answer key slide")
1919
-
1920
- for i, slide in enumerate(st.session_state.slides_data):
1921
- col1, col2 = st.columns([4, 1])
1922
- with col1:
1923
- st.subheader(f"Slide {i+1}: {slide['title']}")
1924
- st.markdown("**Content:**")
1925
- for point in slide.get('content', []):
1926
- st.markdown(f"- {point}")
1927
- if slide.get('notes'):
1928
- st.markdown(f"**Notes:** {slide['notes']}")
1929
-
1930
- with col2:
1931
- if st.button(f"Regenerate", key=f"reg_{i}"):
1932
- with st.spinner(f"Regenerating slide {i+1}..."):
1933
- new_slide = regenerate_slide(i, topic, slide_count, theme, ppt_version)
1934
- st.session_state.slides_data[i] = new_slide
1935
- st.session_state.regenerate_map[i] = True
1936
- st.rerun()
1937
 
1938
- # Show regeneration status
1939
- if st.session_state.regenerate_map.get(i, False):
1940
- st.success("Regenerated!")
1941
-
1942
- st.markdown("---")
1943
-
1944
- if st.session_state.questionnaire:
1945
- st.subheader("Questionnaire")
1946
- st.markdown(f"**Total Questions:** {len(st.session_state.questionnaire)}")
1947
- for i, q in enumerate(st.session_state.questionnaire, 1):
1948
- st.subheader(f"Question {i}")
1949
- st.markdown(f"**{q['text']}**")
1950
- for j, opt in enumerate(q['options']):
1951
- st.markdown(f"{chr(65+j)}. {opt}")
1952
- st.markdown(f"*Correct: {q['correct']}*")
1953
- st.markdown("---")
1954
-
1955
- if st.session_state.answer_key:
1956
- st.subheader("Answer Key")
1957
- for i, ans in enumerate(st.session_state.answer_key, 1):
1958
- st.markdown(f"{i}. {ans}")
1959
-
1960
- # Rebuild PPTX if any slides were regenerated
1961
- if any(st.session_state.regenerate_map.values()):
1962
- with st.spinner("Updating presentation with regenerated slides..."):
1963
- pptx_file = create_detailed_pptx(
1964
- st.session_state.slides_data,
1965
- st.session_state.questionnaire,
1966
- theme,
1967
- ppt_version=ppt_version
1968
- )
1969
- st.session_state.pptx_file = pptx_file
1970
- st.session_state.regenerate_map = {i: False for i in range(len(st.session_state.slides_data))}
1971
- st.success("Presentation updated with regenerated slides!")
1972
-
1973
- # Download button
1974
- if st.session_state.pptx_file:
1975
- st.download_button(
1976
- label="Download PowerPoint",
1977
- data=st.session_state.pptx_file,
1978
- file_name=f"{topic.replace(' ', '_') if topic else 'presentation'}_presentation.pptx",
1979
- mime="application/vnd.openxmlformats-officedocument.presentationml.presentation"
1980
- )
1981
 
1982
  if __name__ == "__main__":
1983
  main()
 
1010
 
1011
 
1012
  ###################################################################################################################################################################
1013
+
1014
  import streamlit as st
1015
  import google.generativeai as genai
1016
  from pptx import Presentation
 
1018
  from pptx.dml.color import RGBColor
1019
  from pptx.enum.text import PP_ALIGN, PP_PARAGRAPH_ALIGNMENT
1020
  from pptx.enum.text import MSO_AUTO_SIZE
 
1021
  from pptx.oxml.xmlchemy import OxmlElement
1022
  import io
1023
  import re
1024
  import tempfile
1025
  import os
 
 
 
 
1026
 
1027
  # Load the API key securely from environment variable
1028
  api_key = os.getenv("GOOGLE_API_KEY")
 
1039
  "text_color": RGBColor(200, 200, 200),
1040
  "accent": RGBColor(0, 112, 192),
1041
  "title_font": "Calibri",
1042
+ "text_font": "Calibri"
 
1043
  },
1044
  "Modern Green": {
1045
  "background": RGBColor(22, 82, 66),
 
1047
  "text_color": RGBColor(220, 220, 220),
1048
  "accent": RGBColor(76, 175, 80),
1049
  "title_font": "Arial",
1050
+ "text_font": "Arial"
 
1051
  },
1052
  "Light Corporate": {
1053
  "background": RGBColor(255, 255, 255),
 
1055
  "text_color": RGBColor(33, 33, 33),
1056
  "accent": RGBColor(25, 118, 210),
1057
  "title_font": "Segoe UI",
1058
+ "text_font": "Segoe UI"
 
1059
  },
1060
  "Dark Tech": {
1061
  "background": RGBColor(33, 33, 33),
 
1063
  "text_color": RGBColor(200, 200, 200),
1064
  "accent": RGBColor(0, 150, 255),
1065
  "title_font": "Consolas",
1066
+ "text_font": "Consolas"
 
1067
  }
1068
  }
1069
 
 
 
 
 
 
 
 
1070
  def hex_to_rgb(hex_color):
1071
  """Convert hex color to RGBColor"""
1072
  hex_color = hex_color.lstrip('#')
 
1086
  "accent": RGBColor(79, 129, 189), # Default blue
1087
  "title_font": "Calibri",
1088
  "text_font": "Calibri",
 
1089
  "template_path": tmp_file_path # Store the template path for later use
1090
  }
1091
 
 
1126
 
1127
  return theme
1128
 
1129
+ def generate_slide_content(topic, slide_count):
1130
+ model = genai.GenerativeModel('gemini-2.0-flash')
 
 
 
 
1131
  prompt = f"""Create a comprehensive presentation on '{topic}' with exactly {slide_count} slides.
1132
  For each slide, provide:
1133
  1. A clear title in [Title:] format
 
1198
  Begin the content generation now.
1199
  """
1200
 
1201
+ response = model.generate_content(prompt)
1202
  return response.text
1203
 
1204
  def parse_slide_content(slide_text):
 
1295
 
1296
  return slides, questionnaire, answer_key
1297
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1298
  def create_question_slide(prs, question, question_num, theme):
1299
+ """Create a slide for a single question"""
1300
+ slide = prs.slides.add_slide(prs.slide_layouts[1]) # Title + Content layout
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1301
 
1302
  # Set title
1303
+ title = slide.shapes.title
1304
+ title.text = f"Question {question_num}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1305
 
1306
+ # Only apply custom formatting if not using a template
1307
+ if "template_path" not in theme:
1308
+ title.text_frame.paragraphs[0].font.color.rgb = theme["accent"]
1309
+ title.text_frame.paragraphs[0].font.size = Pt(36)
1310
+ title.text_frame.paragraphs[0].font.bold = True
1311
+ if "title_font" in theme:
1312
+ title.text_frame.paragraphs[0].font.name = theme["title_font"]
1313
+
1314
+ # Create content
1315
+ body = slide.placeholders[1]
1316
+ tf = body.text_frame
1317
+ tf.clear()
1318
+
1319
+ # Add question text
1320
+ p = tf.add_paragraph()
1321
+ p.text = question['text']
1322
+ p.level = 0
1323
+ p.font.bold = True
1324
+ p.space_after = Pt(24)
1325
+
1326
+ # Only apply custom formatting if not using a template
1327
+ if "template_path" not in theme:
1328
+ p.font.color.rgb = theme["text_color"]
1329
+ p.font.size = Pt(24)
1330
+ if "text_font" in theme:
1331
+ p.font.name = theme["text_font"]
1332
 
1333
+ # Add options
1334
  for j, opt in enumerate(question['options']):
1335
  p = tf.add_paragraph()
1336
  p.text = f"{chr(65+j)}. {opt}"
1337
  p.level = 0
 
1338
  p.space_after = Pt(12)
1339
 
1340
+ # Only apply custom formatting if not using a template
1341
  if "template_path" not in theme:
1342
  p.font.color.rgb = theme["text_color"]
1343
+ p.font.size = Pt(20)
1344
  if "text_font" in theme:
1345
  p.font.name = theme["text_font"]
1346
 
1347
  # Add correct answer to speaker notes
1348
+ notes_slide = slide.notes_slide
1349
+ notes_text = f"Correct Answer: {question['correct']}\n"
1350
+ notes_slide.notes_text_frame.text = notes_text
 
 
1351
 
1352
  return slide
1353
 
1354
  def create_answer_key_slide(prs, questions, theme):
1355
+ """Create answer key slide with all correct answers"""
1356
+ slide = prs.slides.add_slide(prs.slide_layouts[1]) # Title + Content layout
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1357
 
1358
  # Set title
1359
+ title = slide.shapes.title
1360
+ title.text = "Answer Key"
 
 
 
 
 
 
 
 
 
 
1361
 
1362
+ # Only apply custom formatting if not using a template
1363
+ if "template_path" not in theme:
1364
+ title.text_frame.paragraphs[0].font.color.rgb = theme["accent"]
1365
+ title.text_frame.paragraphs[0].font.size = Pt(36)
1366
+ title.text_frame.paragraphs[0].font.bold = True
1367
+ if "title_font" in theme:
1368
+ title.text_frame.paragraphs[0].font.name = theme["title_font"]
1369
 
1370
+ # Create content
1371
+ body = slide.placeholders[1]
1372
+ tf = body.text_frame
1373
+ tf.clear()
1374
 
1375
  # Add answer key
1376
  for i, q in enumerate(questions):
1377
  p = tf.add_paragraph()
1378
  p.text = f"Question {i+1}: {q['correct']}"
1379
  p.level = 0
1380
+ p.space_after = Pt(12)
1381
 
1382
+ # Only apply custom formatting if not using a template
1383
  if "template_path" not in theme:
1384
  p.font.color.rgb = theme["text_color"]
1385
  p.font.size = Pt(24)
 
1386
  if "text_font" in theme:
1387
  p.font.name = theme["text_font"]
1388
 
1389
  return slide
1390
 
1391
+ def create_detailed_pptx(slides_data, questions, theme, branding_options=None):
1392
  """Create PowerPoint using the uploaded template"""
1393
  # Use the template if one was uploaded
1394
  if "template_path" in theme and os.path.exists(theme["template_path"]):
 
1396
  else:
1397
  prs = Presentation()
1398
 
1399
+ # Set widescreen layout (16:9 aspect ratio)
1400
+ prs.slide_width = Inches(13.33)
1401
+ prs.slide_height = Inches(7.5)
 
1402
 
1403
  # Layout mapping
1404
  layout_indices = {
 
1416
  default_layout_idx = available_layouts.get('title-content', 0)
1417
 
1418
  # Create main slides
1419
+ for slide_info in slides_data:
1420
  layout = slide_info.get('layout', 'title-content').lower()
1421
  layout_idx = available_layouts.get(layout, default_layout_idx)
1422
 
 
1425
  except IndexError:
1426
  slide = prs.slides.add_slide(prs.slide_layouts[default_layout_idx])
1427
 
1428
+ # Set title
1429
+ title = slide.shapes.title
1430
+ title.text = slide_info['title']
1431
+ title.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER
1432
+
1433
+ # Only apply custom formatting if not using a template
1434
  if "template_path" not in theme:
1435
+ # Apply background
1436
  background = slide.background
1437
  fill = background.fill
1438
  fill.solid()
1439
  fill.fore_color.rgb = theme["background"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1440
 
1441
+ # Format title
1442
+ title.text_frame.paragraphs[0].font.color.rgb = theme["title_color"]
1443
+ title.text_frame.paragraphs[0].font.size = Pt(36)
1444
+ title.text_frame.paragraphs[0].font.bold = True
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1445
  if "title_font" in theme:
1446
+ title.text_frame.paragraphs[0].font.name = theme["title_font"]
1447
+
1448
+ # Add logo if provided
1449
+ if branding_options and branding_options.get('logo_path'):
1450
+ add_logo_to_slide(slide, branding_options['logo_path'],
1451
+ branding_options.get('logo_position', 'top-right'))
1452
 
1453
  # Set content based on layout
1454
+ if layout_idx == 3: # Two column layout
1455
  content = slide_info.get('content', [])
1456
  mid_point = len(content) // 2
1457
  left_content = content[:mid_point]
1458
  right_content = content[mid_point:]
1459
 
1460
+ left_body = slide.placeholders[1]
1461
+ left_tf = left_body.text_frame
1462
+ left_tf.clear()
 
 
 
 
1463
 
1464
+ right_body = slide.placeholders[2]
1465
+ right_tf = right_body.text_frame
1466
+ right_tf.clear()
 
1467
 
1468
  for content_part, tf in [(left_content, left_tf), (right_content, right_tf)]:
1469
  for point in content_part:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1470
  p = tf.add_paragraph()
1471
+ point_text = point.replace('- ', '').strip()
1472
+ p.text = point_text
1473
  p.level = 0
1474
+ p.alignment = PP_ALIGN.JUSTIFY
 
1475
  if "template_path" not in theme: # Only apply custom formatting if no template
1476
  p.font.color.rgb = theme["text_color"]
1477
+ p.font.size = Pt(18)
1478
  if "text_font" in theme:
1479
  p.font.name = theme["text_font"]
1480
 
1481
+ elif layout_idx != 0: # Not title-only
1482
+ body = slide.placeholders[1]
1483
+ tf = body.text_frame
1484
+ tf.clear()
1485
+
1486
+ for point in slide_info.get('content', []):
1487
+ p = tf.add_paragraph()
1488
+ point_text = point.replace('- ', '').strip()
1489
+ p.text = point_text
1490
+ p.level = 0
1491
+ p.alignment = PP_ALIGN.JUSTIFY
1492
+ if "template_path" not in theme: # Only apply custom formatting if no template
1493
+ p.font.color.rgb = theme["text_color"]
1494
+ p.font.size = Pt(18)
1495
+ if "text_font" in theme:
1496
+ p.font.name = theme["text_font"]
1497
+
1498
  # Add notes if available
1499
+ if slide_info.get('notes'):
1500
  notes_slide = slide.notes_slide
1501
+ notes_slide.notes_text_frame.text = slide_info['notes']
 
1502
 
1503
+ # Add question slides (one per question)
1504
  if questions:
1505
+ # Add section header
1506
+ section_slide = prs.slides.add_slide(prs.slide_layouts[2]) # Section header layout
1507
+ section_title = section_slide.shapes.title
1508
+ section_title.text = "Knowledge Check"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1509
 
1510
  # Add each question on a separate slide
1511
  for i, question in enumerate(questions, 1):
 
1524
 
1525
  return pptx_io
1526
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1527
  def main():
1528
  st.set_page_config(page_title="Advanced PPTX Generator", layout="wide")
1529
 
 
1534
  if 'custom_themes' not in st.session_state:
1535
  st.session_state.custom_themes = {}
1536
 
 
 
 
 
 
 
 
 
 
 
 
 
1537
  # Combine default and custom themes
1538
  ALL_THEMES = {**DEFAULT_THEMES, **st.session_state.custom_themes}
1539
  col1, col2 = st.columns([3, 1])
 
1568
  accent_color = st.color_picker("Accent Color", "#0070C0")
1569
  title_font = st.text_input("Title Font", "Calibri")
1570
  text_font = st.text_input("Text Font", "Calibri")
 
1571
 
1572
  if st.form_submit_button("Save Custom Theme"):
1573
  if new_theme_name:
 
1577
  "text_color": hex_to_rgb(text_color),
1578
  "accent": hex_to_rgb(accent_color),
1579
  "title_font": title_font,
1580
+ "text_font": text_font
 
1581
  }
1582
  st.success(f"Theme '{new_theme_name}' saved successfully!")
1583
  else:
 
1592
  st.success("Theme extracted from template!")
1593
  else:
1594
  st.info("Please upload a PowerPoint file to extract its theme")
 
 
 
 
 
 
 
 
1595
 
1596
  with col2:
1597
  st.markdown("### Instructions")
 
1599
  st.markdown("2. Select slide count and theme")
1600
  st.markdown("3. Click 'Generate Presentation'")
1601
  st.markdown("4. Download your PowerPoint file")
 
1602
 
1603
+ if st.button("Generate Presentation", type="primary", key="generate_btn"):
 
1604
  if not topic:
1605
  st.warning("Please enter a topic first!")
1606
  elif theme_option == "Example-Based Theme" and not uploaded_file:
1607
  st.warning("Please upload a PowerPoint template file first")
1608
  else:
1609
+ with st.spinner(f"Creating {slide_count}-slide presentation about '{topic}'..."):
1610
+ try:
1611
+ slide_text = generate_slide_content(topic, slide_count)
1612
+ slides_data, questionnaire, answer_key = parse_slide_content(slide_text)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1613
 
1614
+ # Show slide overview with detailed content
1615
+ with st.expander("Slide Overview (Detailed)"):
1616
+ for i, slide in enumerate(slides_data, 1):
1617
+ st.subheader(f"Slide {i}: {slide['title']}")
1618
+ st.markdown("**Content:**")
1619
+ for point in slide.get('content', []):
1620
+ st.markdown(f"- {point}")
1621
+ if slide.get('notes'):
1622
+ st.markdown(f"**Notes:** {slide['notes']}")
1623
+ st.markdown("---")
1624
+
1625
+ if questionnaire:
1626
+ st.subheader("Questionnaire")
1627
+ for i, q in enumerate(questionnaire, 1):
1628
+ st.subheader(f"Question {i}")
1629
+ st.markdown(f"**{q['text']}**")
1630
+ for j, opt in enumerate(q['options']):
1631
+ st.markdown(f"{chr(65+j)}. {opt}")
1632
+ st.markdown(f"*Correct: {q['correct']}*")
1633
+ st.markdown("---")
1634
+
1635
+ if answer_key:
1636
+ st.subheader("Answer Key")
1637
+ for i, ans in enumerate(answer_key, 1):
1638
+ st.markdown(f"{i}. {ans}")
1639
+
1640
+ pptx_file = create_detailed_pptx(slides_data, questionnaire, theme)
1641
+
1642
+ st.success("Presentation generated successfully!")
1643
+
1644
+ st.download_button(
1645
+ label="Download PowerPoint",
1646
+ data=pptx_file,
1647
+ file_name=f"{topic.replace(' ', '_')}_presentation.pptx",
1648
+ mime="application/vnd.openxmlformats-officedocument.presentationml.presentation"
1649
+ )
1650
+
1651
+ except Exception as e:
1652
+ st.error(f"An error occurred: {str(e)}")
 
 
 
 
1653
 
1654
  if __name__ == "__main__":
1655
  main()