Enhance-PPTX / app.py
usmanyousaf's picture
Update app.py
8f51465 verified
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()