Files
openclaw-oci-pptx/project/generate_openclaw_ppt_template.py
2026-02-27 09:28:39 -03:00

233 lines
8.0 KiB
Python

from __future__ import annotations
import argparse
import json
import os
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Dict, Any, Optional
from pptx import Presentation
# ============================================================
# PATHS / CONFIG (flexible via env vars and CLI)
# ============================================================
def _env_path(name: str, default: Optional[str] = None) -> Optional[Path]:
v = os.getenv(name, default)
return Path(v).expanduser() if v else None
OPENCLAW_WORKDIR = _env_path("OPENCLAW_WORKDIR", ".") # default: current directory
PPTX_TEMPLATE_PATH = _env_path(
"PPTX_TEMPLATE_PATH",
str(OPENCLAW_WORKDIR / "template_openclaw_oci_clean.pptx"),
)
PPTX_OUTPUT_PATH = _env_path(
"PPTX_OUTPUT_PATH",
str(OPENCLAW_WORKDIR / "openclaw_oci_presentation.pptx"),
)
# Prefer OCI_CONTENT_FILE (policy name) but accept PPTX_CONTENT_PATH too
PPTX_CONTENT_PATH = _env_path(
"OCI_CONTENT_FILE",
os.getenv("PPTX_CONTENT_PATH", str(OPENCLAW_WORKDIR / "content.json")),
)
DEFAULT_LINK = "https://docs.oracle.com/en-us/iaas/Content/generative-ai/home.htm"
DEFAULT_PRESENTER = os.getenv("PPTX_PRESENTER", "Cristiano Hoshikawa")
DEFAULT_COVER_SUBTITLE = os.getenv("PPTX_COVER_SUBTITLE", "Architecture")
# ============================================================
# TEMPLATE ENGINE
# ============================================================
class RedwoodSafePPT:
"""
Loads a PPTX template, wipes all existing slides safely, and builds a new deck
using named layouts from the template.
"""
LAYOUT_COVER = "Cover 1 - Full Image"
LAYOUT_CONTENT = "Full Page - Light"
def __init__(self, template_path: Path):
template_path = Path(template_path).expanduser()
if not template_path.exists():
raise FileNotFoundError(f"Template not found: {template_path}")
self.prs = Presentation(str(template_path))
# Remove ALL slides
while len(self.prs.slides) > 0:
rId = self.prs.slides._sldIdLst[0].rId
self.prs.part.drop_rel(rId)
del self.prs.slides._sldIdLst[0]
self.layouts = {layout.name: layout for layout in self.prs.slide_layouts}
def _layout(self, name: str):
if name not in self.layouts:
available = ", ".join(sorted(self.layouts.keys()))
raise ValueError(f"Layout '{name}' not found in template. Available: {available}")
return self.layouts[name]
def add_content(self, title: str, subhead: str, body: str):
slide = self.prs.slides.add_slide(self._layout(self.LAYOUT_CONTENT))
text_placeholders = [ph for ph in slide.placeholders if getattr(ph, "has_text_frame", False)]
if len(text_placeholders) < 2:
raise RuntimeError("Content layout must have at least 2 text placeholders.")
text_placeholders[0].text = title
text_placeholders[1].text = f"{subhead}\n\n{body}"
def add_cover(self, title: str, subtitle: str, presenter: str):
slide = self.prs.slides.add_slide(self._layout(self.LAYOUT_COVER))
text_placeholders = [ph for ph in slide.placeholders if getattr(ph, "has_text_frame", False)]
if len(text_placeholders) < 2:
raise RuntimeError("Cover layout must have at least 2 text placeholders.")
text_placeholders[0].text = title
text_placeholders[1].text = subtitle
# Optional placeholders by name
for ph in text_placeholders:
name = (getattr(ph, "name", "") or "").lower()
if "date" in name:
ph.text = datetime.now().strftime("%d %b %Y")
if "presenter" in name:
ph.text = presenter
def save(self, output_path: Path):
output_path = Path(output_path).expanduser()
output_path.parent.mkdir(parents=True, exist_ok=True)
if output_path.exists():
output_path.unlink()
self.prs.save(str(output_path))
# ============================================================
# DECK (fixed 7 slides)
# ============================================================
class OCIStrategicArchitectDeck:
def __init__(self, template_path: Path):
self.ppt = RedwoodSafePPT(template_path)
def _format_section(self, section: Dict[str, Any]) -> str:
bullets = section.get("bullets", []) or []
evidence = section.get("evidence", []) or []
keywords = section.get("keywords", []) or []
lines = []
for b in bullets:
lines.append(f"{str(b).strip()}")
if evidence:
lines.append("")
lines.append("Evidence:")
for e in evidence[:2]:
lines.append(f"- {str(e).strip()}")
if keywords:
lines.append("")
lines.append("Keywords: " + ", ".join([str(k).strip() for k in keywords[:8]]))
return "\n".join(lines).strip()
def build(self, material_link: str, content: Dict[str, Any], presenter: str, cover_subtitle: str):
# 1) Cover
self.ppt.add_cover(
title=str(content["cover_title"]).strip(),
subtitle=cover_subtitle,
presenter=presenter,
)
# 2) Intro
self.ppt.add_content(
title="Intro",
subhead="Context and Motivation",
body=self._format_section(content["introduction"]),
)
# 3) Technologies
self.ppt.add_content(
title="Technologies",
subhead="Stack OCI",
body=self._format_section(content["technologies"]),
)
# 4) Architecture
self.ppt.add_content(
title="Architecture",
subhead="Architecture Flow",
body=self._format_section(content["architecture"]),
)
# 5) Problems
self.ppt.add_content(
title="Problems",
subhead="Technical Challenges",
body=self._format_section(content["problems"]),
)
# 6) Demo
self.ppt.add_content(
title="Demo",
subhead="Materials",
body=f"{material_link}\n\n{self._format_section(content['demo'])}",
)
# 7) Conclusion
self.ppt.add_content(
title="Conclusion",
subhead="Strategies",
body=self._format_section(content["conclusion"]),
)
if len(self.ppt.prs.slides) != 7:
raise RuntimeError("Deck must contain exactly 7 slides.")
def save(self, output_path: Path):
self.ppt.save(output_path)
# ============================================================
# CLI
# ============================================================
def _load_json(path: Path) -> Dict[str, Any]:
path = Path(path).expanduser()
if not path.exists():
raise FileNotFoundError(f"content.json not found: {path}")
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def main():
parser = argparse.ArgumentParser(description="Generate a 7-slide OCI strategic PPTX from a template + content.json.")
parser.add_argument("--template", default=str(PPTX_TEMPLATE_PATH), help="Path to the PPTX template file.")
parser.add_argument("--output", default=str(PPTX_OUTPUT_PATH), help="Path to the output PPTX to be written.")
parser.add_argument("--content", default=str(PPTX_CONTENT_PATH), help="Path to content.json.")
parser.add_argument("--link", default=os.getenv("OCI_LINK_DEMO", DEFAULT_LINK), help="Source link shown on Demo slide.")
parser.add_argument("--presenter", default=DEFAULT_PRESENTER, help="Presenter name on cover (if placeholder exists).")
parser.add_argument("--cover-subtitle", default=DEFAULT_COVER_SUBTITLE, help="Cover subtitle.")
args = parser.parse_args()
content = _load_json(Path(args.content))
deck = OCIStrategicArchitectDeck(Path(args.template))
deck.build(args.link, content, presenter=args.presenter, cover_subtitle=args.cover_subtitle)
deck.save(Path(args.output))
print("✅ PPT generated:", Path(args.output).expanduser().resolve())
if __name__ == "__main__":
main()