AI Engineer
Deploy Streamlit Content Agent
0b29030
Raw
History Blame Contribute Delete
3.37 kB
"""Generate a branded .pptx from a structured outline.
The LLM only produces a PPTOutline JSON (title, subtitle, slides[]); rendering
is deterministic via python-pptx so the deck is consistent and cheap.
"""
from __future__ import annotations
import os
from typing import Dict, Any
from pptx import Presentation
from pptx.util import Inches, Pt
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN
from .schemas import FactPack, Brief
from .llm import LLMClient
OUTLINE_SYSTEM = (
"You create a concise slide outline for a product/marketing update deck. "
"Use ONLY the provided facts. Respond ONLY as JSON: {\"title\": str, "
"\"subtitle\": str, \"slides\": [{\"title\": str, \"bullets\": [str]}]}. "
"Aim for 4-6 slides, 2-4 bullets each."
)
BRAND_COLOR = RGBColor(0x2E, 0x5B, 0xFF)
DARK = RGBColor(0x1A, 0x1A, 0x2E)
GRAY = RGBColor(0x55, 0x55, 0x66)
def build_outline(fact_pack: FactPack, brief: Brief, llm: LLMClient) -> Dict[str, Any]:
user = (
f"FACTS: {fact_pack.approved_facts}\nPROOF: {fact_pack.approved_proof}\n"
f"UPDATE: {fact_pack.user_update}\nOBJECTIVE: {brief.objective}"
)
return llm.generate_json("ppt_outline", OUTLINE_SYSTEM, user,
context={"user_update": fact_pack.user_update})
def render_pptx(outline: Dict[str, Any], out_path: str) -> str:
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
blank = prs.slide_layouts[6]
# --- Title slide ---
slide = prs.slides.add_slide(blank)
_band(slide, prs)
tb = slide.shapes.add_textbox(Inches(0.8), Inches(2.6), Inches(11.7), Inches(2))
tf = tb.text_frame
tf.word_wrap = True
p = tf.paragraphs[0]
p.text = outline.get("title", "Product Update")
p.font.size = Pt(46)
p.font.bold = True
p.font.color.rgb = DARK
sub = tf.add_paragraph()
sub.text = outline.get("subtitle", "")
sub.font.size = Pt(22)
sub.font.color.rgb = GRAY
# --- Content slides ---
for s in outline.get("slides", []):
slide = prs.slides.add_slide(blank)
_band(slide, prs, thin=True)
head = slide.shapes.add_textbox(Inches(0.8), Inches(0.6), Inches(11.7), Inches(1))
hp = head.text_frame.paragraphs[0]
hp.text = s.get("title", "")
hp.font.size = Pt(32)
hp.font.bold = True
hp.font.color.rgb = BRAND_COLOR
body = slide.shapes.add_textbox(Inches(1.0), Inches(1.8), Inches(11.3), Inches(5))
bf = body.text_frame
bf.word_wrap = True
for i, bullet in enumerate(s.get("bullets", [])):
para = bf.paragraphs[0] if i == 0 else bf.add_paragraph()
para.text = "• " + str(bullet)
para.font.size = Pt(20)
para.font.color.rgb = DARK
para.space_after = Pt(12)
os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True)
prs.save(out_path)
return out_path
def _band(slide, prs, thin: bool = False):
"""Add a brand-colored accent band at the top of a slide."""
from pptx.enum.shapes import MSO_SHAPE
height = Inches(0.25) if thin else Inches(0.6)
shape = slide.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, prs.slide_width, height)
shape.fill.solid()
shape.fill.fore_color.rgb = BRAND_COLOR
shape.line.fill.background()