Spaces:
Sleeping
Sleeping
update ppt side formate
Browse files- pages/1_π_Text_to_PPT.py +190 -55
pages/1_π_Text_to_PPT.py
CHANGED
@@ -1010,7 +1010,6 @@
|
|
1010 |
|
1011 |
|
1012 |
###################################################################################################################################################################
|
1013 |
-
|
1014 |
import streamlit as st
|
1015 |
import google.generativeai as genai
|
1016 |
from pptx import Presentation
|
@@ -1025,6 +1024,9 @@ import re
|
|
1025 |
import tempfile
|
1026 |
import os
|
1027 |
from PIL import Image
|
|
|
|
|
|
|
1028 |
|
1029 |
# Load the API key securely from environment variable
|
1030 |
api_key = os.getenv("GOOGLE_API_KEY")
|
@@ -1069,6 +1071,13 @@ DEFAULT_THEMES = {
|
|
1069 |
}
|
1070 |
}
|
1071 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1072 |
def hex_to_rgb(hex_color):
|
1073 |
"""Convert hex color to RGBColor"""
|
1074 |
hex_color = hex_color.lstrip('#')
|
@@ -1128,8 +1137,12 @@ def extract_theme_from_pptx(uploaded_file):
|
|
1128 |
|
1129 |
return theme
|
1130 |
|
1131 |
-
|
1132 |
-
|
|
|
|
|
|
|
|
|
1133 |
prompt = f"""Create a comprehensive presentation on '{topic}' with exactly {slide_count} slides.
|
1134 |
For each slide, provide:
|
1135 |
1. A clear title in [Title:] format
|
@@ -1200,7 +1213,7 @@ def generate_slide_content(topic, slide_count):
|
|
1200 |
Begin the content generation now.
|
1201 |
"""
|
1202 |
|
1203 |
-
response =
|
1204 |
return response.text
|
1205 |
|
1206 |
def parse_slide_content(slide_text):
|
@@ -1433,7 +1446,7 @@ def create_answer_key_slide(prs, questions, theme):
|
|
1433 |
|
1434 |
return slide
|
1435 |
|
1436 |
-
def create_detailed_pptx(slides_data, questions, theme, branding_options=None):
|
1437 |
"""Create PowerPoint using the uploaded template"""
|
1438 |
# Use the template if one was uploaded
|
1439 |
if "template_path" in theme and os.path.exists(theme["template_path"]):
|
@@ -1441,9 +1454,10 @@ def create_detailed_pptx(slides_data, questions, theme, branding_options=None):
|
|
1441 |
else:
|
1442 |
prs = Presentation()
|
1443 |
|
1444 |
-
# Set
|
1445 |
-
|
1446 |
-
prs.
|
|
|
1447 |
|
1448 |
# Layout mapping
|
1449 |
layout_indices = {
|
@@ -1461,7 +1475,7 @@ def create_detailed_pptx(slides_data, questions, theme, branding_options=None):
|
|
1461 |
default_layout_idx = available_layouts.get('title-content', 0)
|
1462 |
|
1463 |
# Create main slides
|
1464 |
-
for slide_info in slides_data:
|
1465 |
layout = slide_info.get('layout', 'title-content').lower()
|
1466 |
layout_idx = available_layouts.get(layout, default_layout_idx)
|
1467 |
|
@@ -1580,6 +1594,53 @@ def create_detailed_pptx(slides_data, questions, theme, branding_options=None):
|
|
1580 |
|
1581 |
return pptx_io
|
1582 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1583 |
def main():
|
1584 |
st.set_page_config(page_title="Advanced PPTX Generator", layout="wide")
|
1585 |
|
@@ -1590,6 +1651,18 @@ def main():
|
|
1590 |
if 'custom_themes' not in st.session_state:
|
1591 |
st.session_state.custom_themes = {}
|
1592 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1593 |
# Combine default and custom themes
|
1594 |
ALL_THEMES = {**DEFAULT_THEMES, **st.session_state.custom_themes}
|
1595 |
col1, col2 = st.columns([3, 1])
|
@@ -1648,6 +1721,14 @@ def main():
|
|
1648 |
st.success("Theme extracted from template!")
|
1649 |
else:
|
1650 |
st.info("Please upload a PowerPoint file to extract its theme")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1651 |
|
1652 |
with col2:
|
1653 |
st.markdown("### Instructions")
|
@@ -1655,6 +1736,7 @@ def main():
|
|
1655 |
st.markdown("2. Select slide count and theme")
|
1656 |
st.markdown("3. Click 'Generate Presentation'")
|
1657 |
st.markdown("4. Download your PowerPoint file")
|
|
|
1658 |
|
1659 |
if st.button("Generate Presentation", type="primary", key="generate_btn"):
|
1660 |
if not topic:
|
@@ -1662,53 +1744,106 @@ def main():
|
|
1662 |
elif theme_option == "Example-Based Theme" and not uploaded_file:
|
1663 |
st.warning("Please upload a PowerPoint template file first")
|
1664 |
else:
|
1665 |
-
|
1666 |
-
|
1667 |
-
|
1668 |
-
|
1669 |
-
|
1670 |
-
|
1671 |
-
|
1672 |
-
|
1673 |
-
|
1674 |
-
|
1675 |
-
|
1676 |
-
|
1677 |
-
|
1678 |
-
|
1679 |
-
|
1680 |
-
|
1681 |
-
|
1682 |
-
|
1683 |
-
|
1684 |
-
|
1685 |
-
|
1686 |
-
|
1687 |
-
|
1688 |
-
|
1689 |
-
|
1690 |
-
|
1691 |
-
|
1692 |
-
|
1693 |
-
|
1694 |
-
|
1695 |
-
|
1696 |
-
|
1697 |
-
|
1698 |
-
|
1699 |
-
|
1700 |
-
|
1701 |
-
|
1702 |
-
|
1703 |
-
|
1704 |
-
|
1705 |
-
|
1706 |
-
|
1707 |
-
|
1708 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1709 |
|
1710 |
-
|
1711 |
-
st.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1712 |
|
1713 |
if __name__ == "__main__":
|
1714 |
main()
|
|
|
1010 |
|
1011 |
|
1012 |
###################################################################################################################################################################
|
|
|
1013 |
import streamlit as st
|
1014 |
import google.generativeai as genai
|
1015 |
from pptx import Presentation
|
|
|
1024 |
import tempfile
|
1025 |
import os
|
1026 |
from PIL import Image
|
1027 |
+
import time
|
1028 |
+
import json
|
1029 |
+
from hashlib import sha256
|
1030 |
|
1031 |
# Load the API key securely from environment variable
|
1032 |
api_key = os.getenv("GOOGLE_API_KEY")
|
|
|
1071 |
}
|
1072 |
}
|
1073 |
|
1074 |
+
# PowerPoint compatibility options
|
1075 |
+
PPT_VERSIONS = {
|
1076 |
+
"Modern (2013+)": {"aspect_ratio": (13.33, 7.5)},
|
1077 |
+
"Standard (2007-2013)": {"aspect_ratio": (10, 7.5)},
|
1078 |
+
"Legacy (2003)": {"aspect_ratio": (10, 7.5)}
|
1079 |
+
}
|
1080 |
+
|
1081 |
def hex_to_rgb(hex_color):
|
1082 |
"""Convert hex color to RGBColor"""
|
1083 |
hex_color = hex_color.lstrip('#')
|
|
|
1137 |
|
1138 |
return theme
|
1139 |
|
1140 |
+
@st.cache_data(ttl=3600, show_spinner="Generating slide content...")
|
1141 |
+
def generate_slide_content(topic, slide_count, _model=None):
|
1142 |
+
"""Generate slide content with caching"""
|
1143 |
+
if _model is None:
|
1144 |
+
_model = genai.GenerativeModel('gemini-2.0-flash')
|
1145 |
+
|
1146 |
prompt = f"""Create a comprehensive presentation on '{topic}' with exactly {slide_count} slides.
|
1147 |
For each slide, provide:
|
1148 |
1. A clear title in [Title:] format
|
|
|
1213 |
Begin the content generation now.
|
1214 |
"""
|
1215 |
|
1216 |
+
response = _model.generate_content(prompt)
|
1217 |
return response.text
|
1218 |
|
1219 |
def parse_slide_content(slide_text):
|
|
|
1446 |
|
1447 |
return slide
|
1448 |
|
1449 |
+
def create_detailed_pptx(slides_data, questions, theme, branding_options=None, ppt_version="Modern (2013+)"):
|
1450 |
"""Create PowerPoint using the uploaded template"""
|
1451 |
# Use the template if one was uploaded
|
1452 |
if "template_path" in theme and os.path.exists(theme["template_path"]):
|
|
|
1454 |
else:
|
1455 |
prs = Presentation()
|
1456 |
|
1457 |
+
# Set aspect ratio based on selected version
|
1458 |
+
aspect_ratio = PPT_VERSIONS[ppt_version]["aspect_ratio"]
|
1459 |
+
prs.slide_width = Inches(aspect_ratio[0])
|
1460 |
+
prs.slide_height = Inches(aspect_ratio[1])
|
1461 |
|
1462 |
# Layout mapping
|
1463 |
layout_indices = {
|
|
|
1475 |
default_layout_idx = available_layouts.get('title-content', 0)
|
1476 |
|
1477 |
# Create main slides
|
1478 |
+
for i, slide_info in enumerate(slides_data, 1):
|
1479 |
layout = slide_info.get('layout', 'title-content').lower()
|
1480 |
layout_idx = available_layouts.get(layout, default_layout_idx)
|
1481 |
|
|
|
1594 |
|
1595 |
return pptx_io
|
1596 |
|
1597 |
+
def regenerate_slide(slide_index, topic, slide_count, theme, ppt_version):
|
1598 |
+
"""Regenerate a specific slide using AI"""
|
1599 |
+
model = genai.GenerativeModel('gemini-2.0-flash')
|
1600 |
+
prompt = f"""We are creating a presentation on '{topic}' with {slide_count} slides.
|
1601 |
+
Please regenerate only slide number {slide_index+1} with the same structure as the others.
|
1602 |
+
The slide should have:
|
1603 |
+
- A clear title in [Title:] format
|
1604 |
+
- 3-5 detailed bullet points in [Content:] format
|
1605 |
+
- Optional speaker notes in [Notes:] format
|
1606 |
+
- Layout suggestion in [Layout:] format
|
1607 |
+
|
1608 |
+
Format:
|
1609 |
+
[Title:] Slide Title
|
1610 |
+
[Layout:] layout-type
|
1611 |
+
[Content:]
|
1612 |
+
- Point 1
|
1613 |
+
- Point 2
|
1614 |
+
[Notes:] Optional notes
|
1615 |
+
|
1616 |
+
Make sure the content is relevant to the overall topic and flows with the presentation.
|
1617 |
+
"""
|
1618 |
+
|
1619 |
+
response = model.generate_content(prompt)
|
1620 |
+
new_slide_text = response.text
|
1621 |
+
|
1622 |
+
# Parse the new slide
|
1623 |
+
new_slide = {'title': '', 'content': [], 'notes': '', 'layout': 'title-content'}
|
1624 |
+
for line in new_slide_text.split('\n'):
|
1625 |
+
line = line.strip()
|
1626 |
+
if line.startswith('[Title:]'):
|
1627 |
+
new_slide['title'] = line.replace('[Title:]', '').strip()
|
1628 |
+
elif line.startswith('[Content:]'):
|
1629 |
+
pass # Content follows on subsequent lines
|
1630 |
+
elif line.startswith('[Notes:]'):
|
1631 |
+
new_slide['notes'] = line.replace('[Notes:]', '').strip()
|
1632 |
+
elif line.startswith('[Layout:]'):
|
1633 |
+
layout = line.replace('[Layout:]', '').strip().lower()
|
1634 |
+
valid_layouts = ['title-only', 'title-content', 'two-column', 'section-header']
|
1635 |
+
new_slide['layout'] = layout if layout in valid_layouts else 'title-content'
|
1636 |
+
elif line.startswith('-'):
|
1637 |
+
point = line[1:].strip()
|
1638 |
+
if ':' in point:
|
1639 |
+
point = point.split(':')[0].strip()
|
1640 |
+
new_slide['content'].append(point)
|
1641 |
+
|
1642 |
+
return new_slide
|
1643 |
+
|
1644 |
def main():
|
1645 |
st.set_page_config(page_title="Advanced PPTX Generator", layout="wide")
|
1646 |
|
|
|
1651 |
if 'custom_themes' not in st.session_state:
|
1652 |
st.session_state.custom_themes = {}
|
1653 |
|
1654 |
+
# Initialize session state for presentation data
|
1655 |
+
if 'slides_data' not in st.session_state:
|
1656 |
+
st.session_state.slides_data = []
|
1657 |
+
if 'questionnaire' not in st.session_state:
|
1658 |
+
st.session_state.questionnaire = []
|
1659 |
+
if 'answer_key' not in st.session_state:
|
1660 |
+
st.session_state.answer_key = []
|
1661 |
+
if 'pptx_file' not in st.session_state:
|
1662 |
+
st.session_state.pptx_file = None
|
1663 |
+
if 'regenerate_map' not in st.session_state:
|
1664 |
+
st.session_state.regenerate_map = {}
|
1665 |
+
|
1666 |
# Combine default and custom themes
|
1667 |
ALL_THEMES = {**DEFAULT_THEMES, **st.session_state.custom_themes}
|
1668 |
col1, col2 = st.columns([3, 1])
|
|
|
1721 |
st.success("Theme extracted from template!")
|
1722 |
else:
|
1723 |
st.info("Please upload a PowerPoint file to extract its theme")
|
1724 |
+
|
1725 |
+
# PowerPoint version compatibility
|
1726 |
+
ppt_version = st.selectbox(
|
1727 |
+
"PowerPoint Version Compatibility:",
|
1728 |
+
list(PPT_VERSIONS.keys()),
|
1729 |
+
index=0
|
1730 |
+
)
|
1731 |
+
st.caption("Modern (2013+): Widescreen (16:9)\nStandard (2007-2013): Standard (4:3)\nLegacy (2003): Standard (4:3)")
|
1732 |
|
1733 |
with col2:
|
1734 |
st.markdown("### Instructions")
|
|
|
1736 |
st.markdown("2. Select slide count and theme")
|
1737 |
st.markdown("3. Click 'Generate Presentation'")
|
1738 |
st.markdown("4. Download your PowerPoint file")
|
1739 |
+
st.markdown("5. Use 'Regenerate' buttons to improve specific slides")
|
1740 |
|
1741 |
if st.button("Generate Presentation", type="primary", key="generate_btn"):
|
1742 |
if not topic:
|
|
|
1744 |
elif theme_option == "Example-Based Theme" and not uploaded_file:
|
1745 |
st.warning("Please upload a PowerPoint template file first")
|
1746 |
else:
|
1747 |
+
progress_bar = st.progress(0)
|
1748 |
+
status_text = st.empty()
|
1749 |
+
|
1750 |
+
# Generate slide content
|
1751 |
+
status_text.text("Generating content with AI...")
|
1752 |
+
progress_bar.progress(20)
|
1753 |
+
model = genai.GenerativeModel('gemini-2.0-flash')
|
1754 |
+
slide_text = generate_slide_content(topic, slide_count, _model=model)
|
1755 |
+
|
1756 |
+
# Parse slide content
|
1757 |
+
status_text.text("Parsing slide content...")
|
1758 |
+
progress_bar.progress(40)
|
1759 |
+
slides_data, questionnaire, answer_key = parse_slide_content(slide_text)
|
1760 |
+
|
1761 |
+
# Store in session state
|
1762 |
+
st.session_state.slides_data = slides_data
|
1763 |
+
st.session_state.questionnaire = questionnaire
|
1764 |
+
st.session_state.answer_key = answer_key
|
1765 |
+
st.session_state.regenerate_map = {i: False for i in range(len(slides_data))}
|
1766 |
+
|
1767 |
+
# Create PowerPoint
|
1768 |
+
status_text.text("Creating PowerPoint file...")
|
1769 |
+
progress_bar.progress(70)
|
1770 |
+
pptx_file = create_detailed_pptx(slides_data, questionnaire, theme, ppt_version=ppt_version)
|
1771 |
+
st.session_state.pptx_file = pptx_file
|
1772 |
+
|
1773 |
+
progress_bar.progress(100)
|
1774 |
+
status_text.text("Done!")
|
1775 |
+
time.sleep(0.5)
|
1776 |
+
progress_bar.empty()
|
1777 |
+
status_text.empty()
|
1778 |
+
|
1779 |
+
st.success("Presentation generated successfully!")
|
1780 |
+
|
1781 |
+
# Show slide overview with regeneration options
|
1782 |
+
if st.session_state.slides_data:
|
1783 |
+
with st.expander("Slide Overview (Click to Expand)", expanded=True):
|
1784 |
+
st.markdown(f"**Total Slides:** {len(st.session_state.slides_data)} content slides + "
|
1785 |
+
f"{len(st.session_state.questionnaire)} question slides + 1 answer key slide")
|
1786 |
+
|
1787 |
+
for i, slide in enumerate(st.session_state.slides_data):
|
1788 |
+
col1, col2 = st.columns([4, 1])
|
1789 |
+
with col1:
|
1790 |
+
st.subheader(f"Slide {i+1}: {slide['title']}")
|
1791 |
+
st.markdown("**Content:**")
|
1792 |
+
for point in slide.get('content', []):
|
1793 |
+
st.markdown(f"- {point}")
|
1794 |
+
if slide.get('notes'):
|
1795 |
+
st.markdown(f"**Notes:** {slide['notes']}")
|
1796 |
+
|
1797 |
+
with col2:
|
1798 |
+
if st.button(f"Regenerate", key=f"reg_{i}"):
|
1799 |
+
with st.spinner(f"Regenerating slide {i+1}..."):
|
1800 |
+
new_slide = regenerate_slide(i, topic, slide_count, theme, ppt_version)
|
1801 |
+
st.session_state.slides_data[i] = new_slide
|
1802 |
+
st.session_state.regenerate_map[i] = True
|
1803 |
+
st.rerun()
|
1804 |
|
1805 |
+
# Show regeneration status
|
1806 |
+
if st.session_state.regenerate_map.get(i, False):
|
1807 |
+
st.success("Regenerated!")
|
1808 |
+
|
1809 |
+
st.markdown("---")
|
1810 |
+
|
1811 |
+
if st.session_state.questionnaire:
|
1812 |
+
st.subheader("Questionnaire")
|
1813 |
+
st.markdown(f"**Total Questions:** {len(st.session_state.questionnaire)}")
|
1814 |
+
for i, q in enumerate(st.session_state.questionnaire, 1):
|
1815 |
+
st.subheader(f"Question {i}")
|
1816 |
+
st.markdown(f"**{q['text']}**")
|
1817 |
+
for j, opt in enumerate(q['options']):
|
1818 |
+
st.markdown(f"{chr(65+j)}. {opt}")
|
1819 |
+
st.markdown(f"*Correct: {q['correct']}*")
|
1820 |
+
st.markdown("---")
|
1821 |
+
|
1822 |
+
if st.session_state.answer_key:
|
1823 |
+
st.subheader("Answer Key")
|
1824 |
+
for i, ans in enumerate(st.session_state.answer_key, 1):
|
1825 |
+
st.markdown(f"{i}. {ans}")
|
1826 |
+
|
1827 |
+
# Rebuild PPTX if any slides were regenerated
|
1828 |
+
if any(st.session_state.regenerate_map.values()):
|
1829 |
+
with st.spinner("Updating presentation with regenerated slides..."):
|
1830 |
+
pptx_file = create_detailed_pptx(
|
1831 |
+
st.session_state.slides_data,
|
1832 |
+
st.session_state.questionnaire,
|
1833 |
+
theme,
|
1834 |
+
ppt_version=ppt_version
|
1835 |
+
)
|
1836 |
+
st.session_state.pptx_file = pptx_file
|
1837 |
+
st.success("Presentation updated with regenerated slides!")
|
1838 |
+
|
1839 |
+
# Download button
|
1840 |
+
if st.session_state.pptx_file:
|
1841 |
+
st.download_button(
|
1842 |
+
label="Download PowerPoint",
|
1843 |
+
data=st.session_state.pptx_file,
|
1844 |
+
file_name=f"{topic.replace(' ', '_')}_presentation.pptx",
|
1845 |
+
mime="application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
1846 |
+
)
|
1847 |
|
1848 |
if __name__ == "__main__":
|
1849 |
main()
|