#!/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 infographic.""" from __future__ import annotations 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 infographic.", no_args_is_help=True, ) class VisualFormat(StrEnum): AUTO = "auto" INFOGRAPHIC = "infographic" IMAGE = "image" class ThemeSource(StrEnum): CLI = "cli" PR_BODY = "pr-body" NONE = "none" @dataclass(frozen=True) class ThemeSelection: theme: str | None source: ThemeSource 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_infographic_theme(*, 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) return ThemeSelection(theme=None, source=ThemeSource.NONE) def _visual_format_guidance(visual_format: VisualFormat) -> str: if visual_format == VisualFormat.INFOGRAPHIC: return """ Visual mode: infographic/map. Use an infographic or map format. Use structured information design: - data panels, route maps, timeline bands, status badges, legends, checkpoints, before/after boxes, and compact bullet list sections are appropriate. - Organize the source facts into scannable sections with plain-language labels. - Show the before/after value story through layout, hierarchy, and evidence. - Do not render this as a primarily scenic image, movie poster, or painting. """.strip() if visual_format == VisualFormat.IMAGE: return """ Visual mode: regular image/scene. Use a regular image format: actual scene, movie poster, editorial painting, tableau, cover image, or illustrated artifact. Use image-first composition: - Create a single staged visual moment with one strong focal point. - Communicate intent through cinematic staging, editorial metaphor, atmosphere, characters, objects, architecture, landscape, lighting, and motion. - Use at most a short title plus zero to three short labels when text is needed. - Convert process details into visual symbols instead of explanatory boxes. - Do not use data panels, dashboard layouts, timeline strips, flowcharts, legends, before/after boxes, bullet lists, checklist columns, or small explanatory labels. - Do not render an infographic or dense text-heavy infographic. """.strip() return """ Choose the most appropriate visual form: infographic, map, scene, poster, painting, tableau, cover image, illustrated artifact, or another image form that best communicates the PR intent. Choose exactly one visual mode and follow only that mode's rules. Do not blend the modes. Mode A - infographic/map: - Use a readable map backbone with structured information design: sections, route lines, checkpoints, nodes, annotations, status badges, compact evidence bullets, and a legend. - Use this mode when the PR needs several facts, gates, checks, or before/after points to be read explicitly. Mode B - editorial scene/poster/painting: - Use image-first composition: an actual scene, movie poster, painting, tableau, cover image, or symbolic illustrated artifact. - Use this mode when the PR intent can be shown through one staged visual moment with minimal text. - Avoid dashboard layouts, data panels, timeline strips, flowcharts, legends, before/after boxes, bullet lists, and checklist columns in this mode. """.strip() 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,
visual_format: VisualFormat,
theme: str | None,
theme_source: ThemeSource,
prompt: str,
revised_prompt: str | None = None,
) -> str:
theme_section = "_None supplied._" if theme is None else _preformatted(theme)
revised_prompt_section = (
"_Not provided by the Images API._"
if revised_prompt is None
else _preformatted(revised_prompt)
)
return f"""
{PROVENANCE_START}