Spaces:
Running
Running
import streamlit as st | |
from pptx import Presentation | |
from pptx.util import Inches, Pt | |
from pptx.dml.color import RGBColor | |
from pptx.enum.text import PP_ALIGN | |
from groq import Groq | |
import os | |
import json | |
from dotenv import load_dotenv | |
import tempfile | |
from tenacity import retry, stop_after_attempt, wait_fixed | |
import random | |
load_dotenv() | |
# Color palettes inspired by Shutterstock | |
COLOR_PALETTES = [ | |
{ | |
'primary': RGBColor(255, 87, 51), # Orange | |
'secondary': RGBColor(0, 115, 207), # Blue | |
'accent': RGBColor(255, 255, 255), # White | |
'text': RGBColor(51, 51, 51), # Dark Gray | |
'background': RGBColor(242, 242, 242) # Light Gray | |
}, | |
{ | |
'primary': RGBColor(102, 45, 145), # Purple | |
'secondary': RGBColor(255, 230, 0), # Yellow | |
'accent': RGBColor(0, 255, 255), # Cyan | |
'text': RGBColor(51, 51, 51), # Dark Gray | |
'background': RGBColor(230, 230, 250) # Lavender | |
}, | |
{ | |
'primary': RGBColor(0, 176, 80), # Green | |
'secondary': RGBColor(255, 192, 0), # Gold | |
'accent': RGBColor(0, 112, 192), # Blue | |
'text': RGBColor(51, 51, 51), # Dark Gray | |
'background': RGBColor(240, 255, 240) # Honeydew | |
}, | |
{ | |
'primary': RGBColor(192, 0, 0), # Red | |
'secondary': RGBColor(0, 176, 240), # Light Blue | |
'accent': RGBColor(255, 255, 255), # White | |
'text': RGBColor(51, 51, 51), # Dark Gray | |
'background': RGBColor(255, 240, 245) # Lavender Blush | |
}, | |
{ | |
'primary': RGBColor(0, 80, 115), # Dark Blue | |
'secondary': RGBColor(255, 140, 0), # Dark Orange | |
'accent': RGBColor(0, 176, 80), # Green | |
'text': RGBColor(51, 51, 51), # Dark Gray | |
'background': RGBColor(240, 248, 255) # Alice Blue | |
} | |
] | |
def get_random_color_palette(): | |
return random.choice(COLOR_PALETTES) | |
def apply_theme(prs, color_scheme): | |
# Apply theme colors to the master slide | |
background = prs.slide_masters[0].background | |
background.fill.solid() | |
background.fill.fore_color.rgb = color_scheme['background'] | |
# Apply theme colors to placeholders | |
for shape in prs.slide_masters[0].placeholders: | |
if shape.has_text_frame: | |
for paragraph in shape.text_frame.paragraphs: | |
for run in paragraph.runs: | |
run.font.color.rgb = color_scheme['text'] | |
def create_title_slide(prs, title, color_scheme): | |
slide_layout = prs.slide_layouts[0] # Title Slide layout | |
slide = prs.slides.add_slide(slide_layout) | |
title_shape = slide.shapes.title | |
subtitle_shape = slide.placeholders[1] | |
title_shape.text = title | |
title_shape.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER | |
title_shape.text_frame.paragraphs[0].font.color.rgb = color_scheme['primary'] | |
title_shape.text_frame.paragraphs[0].font.size = Pt(44) | |
subtitle_shape.text = "Generated with AI" | |
subtitle_shape.text_frame.paragraphs[0].alignment = PP_ALIGN.CENTER | |
subtitle_shape.text_frame.paragraphs[0].font.color.rgb = color_scheme['secondary'] | |
subtitle_shape.text_frame.paragraphs[0].font.size = Pt(24) | |
def create_content_slide(prs, title, content, color_scheme): | |
slide_layout = prs.slide_layouts[1] # Content with Caption layout | |
slide = prs.slides.add_slide(slide_layout) | |
title_shape = slide.shapes.title | |
title_shape.text = title | |
title_shape.text_frame.paragraphs[0].font.color.rgb = color_scheme['primary'] | |
title_shape.text_frame.paragraphs[0].font.size = Pt(36) | |
content_shape = slide.placeholders[1] | |
tf = content_shape.text_frame | |
tf.clear() # Clear existing text | |
# Add main content | |
p = tf.paragraphs[0] | |
p.text = content['main'] | |
p.font.size = Pt(18) | |
p.font.color.rgb = color_scheme['text'] | |
# Add bullet points | |
for bullet in content['bullets']: | |
p = tf.add_paragraph() | |
p.text = bullet | |
p.level = 1 | |
p.font.size = Pt(16) | |
p.font.color.rgb = color_scheme['text'] | |
# Add a subtle accent to the slide | |
left = Inches(0) | |
top = Inches(6.5) | |
width = prs.slide_width | |
height = Inches(0.5) | |
shape = slide.shapes.add_shape(1, left, top, width, height) | |
shape.fill.solid() | |
shape.fill.fore_color.rgb = color_scheme['accent'] | |
shape.line.color.rgb = color_scheme['accent'] | |
def generate_slides_content(user_input, num_slides): | |
client = Groq(api_key=os.getenv("GROQ_API_KEY")) | |
prompt = f""" | |
Based on the following input, generate a PowerPoint presentation structure with exactly {num_slides} slides. | |
The output should be a JSON array of slides, where each slide is an object with a 'title', 'main' content, and 'bullets' (an array of bullet points). | |
Make sure the content is concise and suitable for a presentation. | |
User Input: {user_input} | |
Example output format: | |
[ | |
{{ | |
"title": "Slide Title", | |
"main": "Main content of the slide", | |
"bullets": ["Bullet point 1", "Bullet point 2", "Bullet point 3"] | |
}}, | |
// ... more slides (total should be {num_slides}) | |
] | |
""" | |
try: | |
chat_completion = client.chat.completions.create( | |
messages=[ | |
{"role": "system", "content": "You are a helpful assistant that generates PowerPoint presentation content and return the content in JSON format."}, | |
{"role": "user", "content": prompt} | |
], | |
model="mixtral-8x7b-32768", | |
temperature=1, | |
max_tokens=3000 | |
) | |
return json.loads(chat_completion.choices[0].message.content) | |
except json.JSONDecodeError: | |
st.error("Error: Invalid JSON response from the AI. Retrying...") | |
raise | |
except Exception as e: | |
st.error(f"An error occurred: {str(e)}. Retrying...") | |
raise | |
def generate_presentation(user_input, num_slides): | |
try: | |
slides_content = generate_slides_content(user_input, num_slides) | |
prs = Presentation() | |
color_scheme = get_random_color_palette() | |
apply_theme(prs, color_scheme) | |
# Create title slide | |
create_title_slide(prs, slides_content[0]['title'], color_scheme) | |
# Create content slides | |
for slide in slides_content[1:]: # Skip the first slide as it's used for the title | |
create_content_slide(prs, slide['title'], {'main': slide['main'], 'bullets': slide['bullets']}, color_scheme) | |
with tempfile.NamedTemporaryFile(delete=False, suffix='.pptx') as tmp: | |
prs.save(tmp.name) | |
return tmp.name | |
except Exception as e: | |
st.error(f"Failed to generate presentation: {str(e)}") | |
return None | |
def main(): | |
st.set_page_config(page_title="PowerPoint Generator", page_icon="📊", layout="wide") | |
st.title("🎨 AI-Powered PowerPoint Generator") | |
st.write("Enter your presentation idea and the number of slides you want, and we'll generate a stylish PowerPoint for you!") | |
user_input = st.text_area("Enter the content idea for your presentation:", height=150) | |
num_slides = st.number_input("Number of slides:", min_value=2, max_value=20, value=5) | |
if st.button("Generate Presentation", use_container_width=True, type="primary"): | |
if user_input and num_slides: | |
with st.spinner("Generating your stylish presentation... This may take a moment."): | |
ppt_file = generate_presentation(user_input, num_slides) | |
if ppt_file: | |
st.success("Presentation generated successfully!") | |
with open(ppt_file, "rb") as file: | |
st.download_button( | |
label="Download PowerPoint", | |
data=file, | |
file_name="generated_presentation.pptx", | |
mime="application/vnd.openxmlformats-officedocument.presentationml.presentation" | |
) | |
else: | |
st.error("Failed to generate the presentation. Please try again.") | |
else: | |
st.warning("Please enter content and specify the number of slides (minimum 2).") | |
if __name__ == "__main__": | |
main() |