Spaces:
Sleeping
Sleeping
| 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, PP_PARAGRAPH_ALIGNMENT | |
| import io | |
| import tempfile | |
| import os | |
| import requests | |
| import re | |
| # Initialize Streamlit | |
| st.set_page_config(page_title="PPTX Smart Enhancer", layout="wide") | |
| # Configuration | |
| GROQ_API_KEY = os.environ.get("GROQ_API_KEY") | |
| MODEL_NAME = "gemma2-9b-it" | |
| # Style Options | |
| FONT_CHOICES = { | |
| "Arial": "Arial", | |
| "Calibri": "Calibri", | |
| "Times New Roman": "Times New Roman", | |
| "Helvetica": "Helvetica", | |
| "Verdana": "Verdana", | |
| "Georgia": "Georgia", | |
| "Courier New": "Courier New", | |
| "Palatino": "Palatino", | |
| "Garamond": "Garamond", | |
| "Trebuchet MS": "Trebuchet MS" | |
| } | |
| COLOR_PALETTES = { | |
| "Professional": {"primary": "#2B579A", "secondary": "#5B9BD5", "text": "#000000", "background": "#FFFFFF"}, | |
| "Modern": {"primary": "#404040", "secondary": "#A5A5A5", "text": "#FFFFFF", "background": "#121212"}, | |
| "Vibrant": {"primary": "#E53935", "secondary": "#FFCDD2", "text": "#212121", "background": "#F5F5F5"}, | |
| "Corporate": {"primary": "#1976D2", "secondary": "#BBDEFB", "text": "#212121", "background": "#FAFAFA"}, | |
| "Creative": {"primary": "#7B1FA2", "secondary": "#CE93D8", "text": "#FFFFFF", "background": "#212121"}, | |
| "Nature": {"primary": "#388E3C", "secondary": "#A5D6A7", "text": "#212121", "background": "#F1F8E9"}, | |
| "Elegant": {"primary": "#5D4037", "secondary": "#BCAAA4", "text": "#FFFFFF", "background": "#3E2723"}, | |
| "Minimal": {"primary": "#000000", "secondary": "#E0E0E0", "text": "#212121", "background": "#FFFFFF"} | |
| } | |
| def get_custom_colors(): | |
| """Get custom colors from user input with background option""" | |
| col1, col2, col3, col4 = st.columns(4) | |
| with col1: | |
| primary = st.color_picker("Primary Color", "#2B579A") | |
| with col2: | |
| secondary = st.color_picker("Secondary Color", "#5B9BD5") | |
| with col3: | |
| text = st.color_picker("Text Color", "#000000") | |
| with col4: | |
| background = st.color_picker("Background Color", "#FFFFFF") | |
| return { | |
| "primary": primary, | |
| "secondary": secondary, | |
| "text": text, | |
| "background": background | |
| } | |
| def get_ai_response(prompt: str) -> str: | |
| """Get formatted content from Groq API.""" | |
| headers = {"Authorization": f"Bearer {GROQ_API_KEY}"} | |
| payload = { | |
| "messages": [{ | |
| "role": "user", | |
| "content": f"{prompt}\n\nReturn well-structured content with clear headings, concise bullet points (max 5 per slide), and no markdown. Use bold/italic via formatting, not symbols." | |
| }], | |
| "model": MODEL_NAME, | |
| "temperature": 0.7 | |
| } | |
| response = requests.post( | |
| "https://api.groq.com/openai/v1/chat/completions", | |
| headers=headers, | |
| json=payload, | |
| timeout=30 | |
| ) | |
| return response.json()["choices"][0]["message"]["content"] | |
| def extract_slide_text(slide) -> str: | |
| """Extract text from slide.""" | |
| return "\n".join( | |
| paragraph.text | |
| for shape in slide.shapes | |
| if hasattr(shape, "text_frame") | |
| for paragraph in shape.text_frame.paragraphs | |
| ) | |
| def split_formatted_runs(text): | |
| """Split text into formatted runs removing markdown.""" | |
| formatted_segments = [] | |
| bold_parts = re.split(r'(\*\*)', text) | |
| bold = False | |
| current_text = [] | |
| for part in bold_parts: | |
| if part == '**': | |
| if current_text: | |
| formatted_segments.append({'text': ''.join(current_text), 'bold': bold, 'italic': False}) | |
| current_text = [] | |
| bold = not bold | |
| else: | |
| italic_parts = re.split(r'(\*)', part) | |
| italic = False | |
| for italic_part in italic_parts: | |
| if italic_part == '*': | |
| if current_text: | |
| formatted_segments.append({'text': ''.join(current_text), 'bold': bold, 'italic': italic}) | |
| current_text = [] | |
| italic = not italic | |
| else: | |
| current_text.append(italic_part) | |
| if current_text: | |
| formatted_segments.append({'text': ''.join(current_text), 'bold': bold, 'italic': italic}) | |
| current_text = [] | |
| return formatted_segments | |
| def apply_formatting(text_frame, content, design_settings): | |
| """Apply proper formatting to text frame with markdown removal.""" | |
| text_frame.clear() | |
| paragraphs = [p for p in content.split('\n') if p.strip()] | |
| for para in paragraphs: | |
| clean_para = re.sub(r'^#+\s*', '', para).strip() | |
| if re.match(r'^(What is|Understanding|What\'s|Drive Theory|Incentive Theory)', clean_para, re.IGNORECASE): | |
| p = text_frame.add_paragraph() | |
| p.text = clean_para | |
| p.font.size = Pt(design_settings["heading_size"]) | |
| p.font.bold = True | |
| p.space_after = Pt(12) | |
| continue | |
| if re.match(r'^[\•\-\*] ', clean_para): | |
| p = text_frame.add_paragraph() | |
| p.level = 0 | |
| content = clean_para[2:].strip() | |
| segments = split_formatted_runs(content) | |
| for seg in segments: | |
| run = p.add_run() | |
| run.text = seg['text'] | |
| run.font.bold = seg['bold'] | |
| run.font.italic = seg['italic'] | |
| p.font.size = Pt(design_settings["body_size"]) | |
| continue | |
| p = text_frame.add_paragraph() | |
| segments = split_formatted_runs(clean_para) | |
| for seg in segments: | |
| run = p.add_run() | |
| run.text = seg['text'] | |
| run.font.bold = seg['bold'] | |
| run.font.italic = seg['italic'] | |
| p.font.size = Pt(design_settings["body_size"]) | |
| p.space_after = Pt(6) | |
| def enhance_slide(slide, user_prompt, design_settings): | |
| """Enhance slide content with proper formatting.""" | |
| original_text = extract_slide_text(slide) | |
| if not original_text.strip(): | |
| return | |
| prompt = f"Improve this slide content:\n{original_text}\n\nInstructions: {user_prompt}" | |
| enhanced_content = get_ai_response(prompt) | |
| title_shape = getattr(slide.shapes, 'title', None) | |
| shapes_to_remove = [s for s in slide.shapes if s != title_shape] | |
| for shape in shapes_to_remove: | |
| sp = shape._element | |
| sp.getparent().remove(sp) | |
| left = Inches(1) if title_shape else Inches(0.5) | |
| top = Inches(1.5) if title_shape else Inches(0.5) | |
| textbox = slide.shapes.add_textbox(left, top, Inches(8), Inches(5)) | |
| text_frame = textbox.text_frame | |
| text_frame.word_wrap = True | |
| apply_formatting(text_frame, enhanced_content, design_settings) | |
| apply_design(slide, design_settings) | |
| def apply_design(slide, design_settings): | |
| """Apply visual design to slide.""" | |
| colors = design_settings["colors"] | |
| for shape in slide.shapes: | |
| if hasattr(shape, "text_frame"): | |
| for paragraph in shape.text_frame.paragraphs: | |
| for run in paragraph.runs: | |
| run.font.name = design_settings["font"] | |
| run.font.color.rgb = RGBColor.from_string(colors["text"][1:]) | |
| if shape == getattr(slide.shapes, 'title', None): | |
| run.font.color.rgb = RGBColor.from_string(colors["primary"][1:]) | |
| run.font.size = Pt(design_settings["title_size"]) | |
| run.font.bold = True | |
| if design_settings["set_background"]: | |
| slide.background.fill.solid() | |
| slide.background.fill.fore_color.rgb = RGBColor.from_string(colors["background"][1:]) | |
| def process_presentation(uploaded_file, user_prompt, design_settings): | |
| """Process and enhance the entire presentation.""" | |
| with tempfile.NamedTemporaryFile(delete=False, suffix=".pptx") as tmp_file: | |
| tmp_file.write(uploaded_file.read()) | |
| tmp_path = tmp_file.name | |
| prs = Presentation(tmp_path) | |
| with st.spinner("Enhancing slides..."): | |
| for slide in prs.slides: | |
| enhance_slide(slide, user_prompt, design_settings) | |
| os.unlink(tmp_path) | |
| return prs | |
| def main(): | |
| st.title("Professional PPTX Enhancer") | |
| uploaded_file = st.file_uploader("Upload PowerPoint", type=["pptx"]) | |
| user_prompt = st.text_area( | |
| "Enhancement Instructions", | |
| placeholder="Example: 'Make headings clear and add bullet points for key items'", | |
| height=100 | |
| ) | |
| with st.expander("🎨 Design Options", expanded=True): | |
| tab1, tab2 = st.tabs(["Preset Themes", "Custom Colors"]) | |
| with tab1: | |
| col1, col2 = st.columns(2) | |
| with col1: | |
| font = st.selectbox("Font", list(FONT_CHOICES.keys()), index=1) | |
| color_palette = st.selectbox("Color Scheme", list(COLOR_PALETTES.keys())) | |
| with col2: | |
| title_size = st.slider("Title Size", 12, 44, 32) | |
| heading_size = st.slider("Heading Size", 12, 36, 24) | |
| body_size = st.slider("Body Text Size", 8, 28, 18) | |
| set_background = st.checkbox("Apply Background", True) | |
| with tab2: | |
| st.write("Create your own color scheme:") | |
| custom_colors = get_custom_colors() | |
| use_custom = st.checkbox("Use Custom Colors") | |
| design_settings = { | |
| "font": FONT_CHOICES[font], | |
| "colors": custom_colors if use_custom else COLOR_PALETTES[color_palette], | |
| "color_palette": "Custom" if use_custom else color_palette, | |
| "title_size": title_size, | |
| "heading_size": heading_size, | |
| "body_size": body_size, | |
| "set_background": set_background | |
| } | |
| if st.button("Enhance Presentation") and uploaded_file and user_prompt: | |
| try: | |
| if not GROQ_API_KEY: | |
| st.error("GROQ API key not found! Please set it in Space settings.") | |
| st.stop() | |
| enhanced_prs = process_presentation(uploaded_file, user_prompt, design_settings) | |
| buffer = io.BytesIO() | |
| enhanced_prs.save(buffer) | |
| buffer.seek(0) | |
| st.success("Done!") | |
| st.download_button( | |
| "Download Enhanced PPTX", | |
| data=buffer, | |
| file_name="enhanced.pptx", | |
| mime="application/vnd.openxmlformats-officedocument.presentationml.presentation" | |
| ) | |
| except Exception as e: | |
| st.error(f"Error: {str(e)}") | |
| if __name__ == "__main__": | |
| main() |