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'] @retry(stop=stop_after_attempt(5), wait=wait_fixed(2)) def generate_slides_content(user_input, num_slides): client = Groq( api_key=os.environ.get("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=8000 ) 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()