#!/usr/bin/env -S uv run --script # /// script # requires-python = ">=3.12" # dependencies = [ # "openai>=1.100.2", # "python-dotenv>=1.1.0", # "typer>=0.9.0", # ] # /// """Build and generate a non-gating BM Bossbot PR image.""" from __future__ import annotations import hashlib import html import re from dataclasses import dataclass from enum import StrEnum from pathlib import Path from typing import Annotated import typer if __package__: from .generate_infographic import ( DEFAULT_MODEL, DEFAULT_QUALITY, DEFAULT_SIZE, generate_image_result, ) else: from generate_infographic import ( DEFAULT_MODEL, DEFAULT_QUALITY, DEFAULT_SIZE, generate_image_result, ) SUMMARY_START = "" SUMMARY_END = "" THEME_START = "" THEME_END = "" PROVENANCE_START = "" PROVENANCE_END = "" app = typer.Typer( add_completion=False, help="Generate a non-gating BM Bossbot PR image.", no_args_is_help=True, ) class ThemeSource(StrEnum): AUTO = "auto" CLI = "cli" PR_BODY = "pr-body" @dataclass(frozen=True) class ThemeSelection: theme: str source: ThemeSource BM_IMAGE_THEME_POOL = ( "computer science college textbook: SICP-style diagrams, automata, compiler " "pipelines, type theory, and annotated chalkboard rigor", "classic literature: sea voyages, gothic manors, Dickensian streets, library " "marginalia, and travel-journal artifacts", "fantasy quest ledger: original guild maps, spellbooks, dungeon keys, tavern " "notices, and artifact inventories with no copyrighted settings", "heavy music editorial: metal, hard rock, punk, techno, soul, or reggae " "tour-poster energy with no direct band logos or likenesses", "knockoff space opera: fleet routes, mission consoles, contraband manifests, " "and practical starship drama with no named fictional universes", "sword-and-sorcery: ruined temples, desert roads, battle standards, ancient " "maps, and heroic silhouettes with no named character likenesses", "comic book cover: original splash-page composition, caption boxes, clean " "halftone texture, and bold issue-cover drama", "French new wave movie poster: stark typography, city streets, jump-cut " "composition, and high-contrast editorial photography cues", "WWII public-information poster: home-front logistics, mobilization arrows, " "bold simplified figures, and no real-world party symbols or hate imagery", "Italian movie poster: hand-painted drama, expressive color, credit-block " "energy, and 1960s or 1970s cinema composition with no actor likenesses", "Shakespearean stage: acts and scenes, court intrigue, stage blocking, " "dramatis personae, backstage cue sheets, and theatrical light", "Greek mythology: temple steps, oracle tablets, constellations, labyrinths, " "ship routes, and original heroic allegory", "noir detective photography: case files, typed evidence labels, civic " "infrastructure, streetlight shadows, and newsroom archive grit", "space exploration and astronomy: celestial atlases, observatory charts, " "orbital mechanics, planetary survey routes, and deep-space mission drama", "editorial painting: abstract, classical landscape, western action, " "chiaroscuro, historical mural, stormy seascape, or allegorical canvas", "classic black-and-white photography: documentary field report, contact " "sheet, street photography, civic infrastructure, and darkroom contrast", "80's action movie poster: smoky backlit warehouses, neon streets, practical " "explosions, mission dossiers, countdowns, and no actor likenesses", "alchemy manuscript: transformation diagrams, annotated symbols, recipe-like " "process artifacts, and illuminated margins", "brutalist civic planning: concrete signage, zoning blocks, transit diagrams, " "infrastructure maps, and stern public-service clarity", ) def extract_bossbot_summary(pr_body: str) -> str: pattern = re.compile( rf"{re.escape(SUMMARY_START)}\s*(.*?)\s*{re.escape(SUMMARY_END)}", flags=re.DOTALL, ) match = pattern.search(pr_body) if not match: raise ValueError("PR body is missing the BM Bossbot summary block") return match.group(1).strip() def extract_infographic_theme(pr_body: str) -> str | None: pattern = re.compile( rf"{re.escape(THEME_START)}\s*(.*?)\s*{re.escape(THEME_END)}", flags=re.DOTALL, ) match = pattern.search(pr_body) if not match: return None theme = match.group(1).strip() return theme or None def select_image_theme( *, pr_number: int, summary: str, pr_body: str, theme_override: str | None, ) -> ThemeSelection: if theme_override: return ThemeSelection(theme=theme_override, source=ThemeSource.CLI) body_theme = extract_infographic_theme(pr_body) if body_theme: return ThemeSelection(theme=body_theme, source=ThemeSource.PR_BODY) seed = f"{pr_number}\n{summary}".encode("utf-8") index = int.from_bytes(hashlib.sha256(seed).digest()[:2], byteorder="big") % len( BM_IMAGE_THEME_POOL ) return ThemeSelection(theme=BM_IMAGE_THEME_POOL[index], source=ThemeSource.AUTO) def _preformatted(value: str) -> str: return f"
{html.escape(value, quote=False)}"
def build_infographic_provenance_block(
*,
pr_number: int,
output_path: Path,
model: str,
size: str,
quality: str,
theme: str,
theme_source: ThemeSource,
) -> str:
return f"""
{PROVENANCE_START}