Skill Tracer
Skills › Scientific Agent Skills › scientific-slides › generate_slide_image.py

generate_slide_image.py

198 lines · skills/scientific-slides/scripts/generate_slide_image.py at 5f2bc03

1#!/usr/bin/env python3
2"""
3Slide image generation using Nano Banana 2.
4
5Generate presentation slides or visuals by describing them in natural language.
6Nano Banana 2 handles everything automatically with smart iterative refinement.
7
8Two modes:
9- Default (full slide): Generate complete slides with title, content, visuals (for PDF workflow)
10- Visual only: Generate just images/figures to place on slides (for PPT workflow)
12Supports attaching reference images for context (Nano Banana 2 will see these).
14Usage:
15 # Generate full slide for PDF workflow
16 python generate_slide_image.py "Title: Introduction\\nKey points: AI, ML, Deep Learning" -o slide_01.png
18 # Generate visual only for PPT workflow
19 python generate_slide_image.py "Neural network diagram" -o figure.png --visual-only
21 # With reference images attached
22 python generate_slide_image.py "Create a slide about this data" -o slide.png --attach chart.png
23"""
25import argparse
26import os
27import subprocess
28import sys
29from pathlib import Path
31# Variables forwarded to the generation subprocess. The child needs the
32# OpenRouter credential; the rest keep networking, TLS, and locale working.
33# Copying the whole parent environment instead would hand the child every
34# unrelated secret that happens to be exported in the calling shell.
35FORWARDED_ENV_VARS = (
36 "PATH", "HOME", "LANG", "LC_ALL", "TMPDIR", "PYTHONPATH",
37 "HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY",
38 "http_proxy", "https_proxy", "no_proxy",
39 "SSL_CERT_FILE", "SSL_CERT_DIR", "REQUESTS_CA_BUNDLE", "CURL_CA_BUNDLE",
40 # Windows needs these for sockets, temp files, and interpreter startup.
41 "SYSTEMROOT", "WINDIR", "COMSPEC", "PATHEXT",
42 "APPDATA", "LOCALAPPDATA", "USERPROFILE", "TEMP", "TMP",
46def resolve_api_key(explicit=None):
47 """Resolve the OpenRouter key from --api-key, the environment, then any .env file.
49 The .env scan walks up from the working directory and finally checks the
50 script's own directory, so running from anywhere inside a project picks up
51 the key at its root. The child process is handed the resolved value through
52 build_subprocess_env, so it never has to repeat this search.
53 """
54 if explicit:
55 return explicit
57 from_env = os.environ.get("OPENROUTER_API_KEY", "").strip()
58 if from_env:
59 return from_env
61 cwd = Path.cwd()
62 for directory in [cwd, *cwd.parents, Path(__file__).resolve().parent]:
63 env_file = directory / ".env"
64 if not env_file.is_file():
65 continue
66 try:
67 content = env_file.read_text(encoding="utf-8", errors="replace")
68 except OSError:
69 continue
70 for raw in content.splitlines():
71 line = raw.strip()
72 if line.startswith("#") or "=" not in line:
73 continue
74 name, _, value = line.partition("=")
75 if name.strip() == "OPENROUTER_API_KEY":
76 value = value.strip().strip('"').strip("'")
77 if value:
78 return value
80 return None
83def build_subprocess_env(api_key):
84 """Return a minimal environment for the AI generation subprocess."""
85 env = {name: os.environ[name] for name in FORWARDED_ENV_VARS if name in os.environ}
86 if api_key:
87 env["OPENROUTER_API_KEY"] = api_key
88 return env
91def main():
92 """Command-line interface."""
93 parser = argparse.ArgumentParser(
94 description="Generate presentation slides or visuals using Nano Banana 2 AI",
95 formatter_class=argparse.RawDescriptionHelpFormatter,
96 epilog="""
97How it works:
98 Describe your slide or visual in natural language.
99 Nano Banana 2 generates it automatically with:
100 - Smart iteration (only regenerates if quality is below threshold)
101 - Quality review by Gemini 3.7 Flash
102 - PNG draft for visual and scientific verification
104Modes:
105 Default (full slide): Generate complete slide with title, content, visuals
106 Use for PDF workflow where each slide is an image
108 Visual only: Generate just the image/figure
109 Use for PPT workflow where you add text separately
111Attachments:
112 Use --attach to provide reference images that Nano Banana 2 will see.
113 This allows you to say "create a slide about this chart" and attach the chart.
115Examples:
116 # Full slide (default) - for PDF workflow
117 python generate_slide_image.py "Title: Machine Learning\\nPoints: supervised, unsupervised, reinforcement" -o slide_01.png
119 # Visual only - for PPT workflow
120 python generate_slide_image.py "Flowchart showing data pipeline" -o figure.png --visual-only
122 # With reference images attached
123 python generate_slide_image.py "Create a slide explaining this chart" -o slide.png --attach chart.png
124 python generate_slide_image.py "Combine these into a comparison" -o compare.png --attach before.png --attach after.png
126 # Multiple slides for PDF
127 python generate_slide_image.py "Title slide: AI Conference 2025" -o slides/01_title.png
128 python generate_slide_image.py "Title: Introduction\\nOverview of deep learning" -o slides/02_intro.png
130Environment Variables:
131 OPENROUTER_API_KEY Required for AI generation
132 """
133 )
135 parser.add_argument("prompt", help="Description of the slide or visual to generate")
136 parser.add_argument("-o", "--output", required=True, help="Output file path")
137 parser.add_argument("--attach", action="append", dest="attachments", metavar="IMAGE",
138 help="Attach image file(s) as context (can use multiple times)")
139 parser.add_argument("--visual-only", action="store_true",
140 help="Generate just the visual/figure (for PPT workflow)")
141 parser.add_argument("--iterations", type=int, default=2,
142 help="Maximum refinement iterations (default: 2, max: 2)")
143 parser.add_argument("--api-key", help="OpenRouter API key (or use OPENROUTER_API_KEY env var)")
144 parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output")
146 args = parser.parse_args()
147 if not 1 <= args.iterations <= 2:
148 parser.error("--iterations must be 1 or 2")
149 if Path(args.output).suffix.lower() != ".png":
150 parser.error("--output must end in .png; format conversion is not supported")
152 # Check for API key — resolves --api-key, the environment, then any .env file
153 api_key = resolve_api_key(args.api_key)
154 if not api_key:
155 print("Error: OPENROUTER_API_KEY not found")
156 print("\nFor AI generation, you need an OpenRouter API key.")
157 print("Get one at: https://openrouter.ai/keys")
158 print("\nSet it with:")
159 print(" export OPENROUTER_API_KEY='your_api_key'")
160 print("\nOr add OPENROUTER_API_KEY=your_api_key to a .env file")
161 print("Or use --api-key flag")
162 sys.exit(1)
164 # Find AI generation script
165 script_dir = Path(__file__).parent
166 ai_script = script_dir / "generate_slide_image_ai.py"
168 if not ai_script.exists():
169 print(f"Error: AI generation script not found: {ai_script}")
170 sys.exit(1)
172 # Build command
173 cmd = [sys.executable, str(ai_script), args.prompt, "-o", args.output]
175 # Add attachments
176 if args.attachments:
177 for att in args.attachments:
178 cmd.extend(["--attach", att])
180 if args.visual_only:
181 cmd.append("--visual-only")
183 cmd.extend(["--iterations", str(args.iterations)])
185 if args.verbose:
186 cmd.append("-v")
188 # Execute — pass API key via environment to avoid exposure in process listings
189 try:
190 result = subprocess.run(cmd, check=False, env=build_subprocess_env(api_key))
191 sys.exit(result.returncode)
192 except Exception as e:
193 print(f"Error executing AI generation: {e}")
194 sys.exit(1)
197if __name__ == "__main__":
198 main()