Skip to content

Commit d302951

Browse files
jaredirishclaudejustfinethanku
authored andcommitted
[recipes] Infographic Generator: turn research docs into visual infographics (NateBJones-Projects#85)
* [recipes] Infographic Generator: turn research docs into visual infographics Second recipe from @jaredirish. Part of the Open Brain Flywheel (capture-process-visualize loop, see Issue NateBJones-Projects#84). Takes any markdown doc or Open Brain thought cluster and generates professional infographic images via Gemini's free-tier API. Auto-chunks content, writes verbose prompts (300+ words each), generates PNGs with specific colors/layout/typography. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [recipes] Fix broken relative links in infographic-generator README ../brain-dump-processor/ → ../panning-for-gold/ ../auto-capture-protocol/ → ../auto-capture/ Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [recipes] Address review feedback on infographic generator - Sync generate.py with working local version (cleaner error handling, fix --redo display counter bug) - Fix auto-capture link: directory doesn't exist until PR NateBJones-Projects#42 merges, so link to the PR instead of a non-existent directory Note: part.as_image() and gemini-2.5-flash-image are both valid per the official google-genai SDK docs. Reviewer concerns on those were based on outdated information. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * [recipes] Fix infographic redo progress output --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Jonathan Edwards <justfinethanku@gmail.com>
1 parent 5a451e4 commit d302951

4 files changed

Lines changed: 485 additions & 0 deletions

File tree

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Infographic Generator
2+
3+
*Turn research docs and Open Brain thoughts into professional infographic images*
4+
5+
Generate infographic images from any markdown document, research analysis, or Open Brain thought cluster using Gemini's free-tier image generation API. Auto-chunks content into logical segments, writes verbose image prompts, generates PNGs, and optionally captures prompts back to Open Brain for future retrieval.
6+
7+
## What It Does
8+
9+
Takes structured or unstructured content and runs a four-step process: **Analyze** the source for logical segments, **Chunk** into one-infographic-per-topic, **Prompt** with verbose image generation instructions (300+ words each with specific colors, layout, typography), and **Generate** actual PNG images via Gemini API. Prompts can be stored in Open Brain for future regeneration or adaptation.
10+
11+
## Prerequisites
12+
13+
- Working Open Brain setup ([guide](../../docs/01-getting-started.md))
14+
- Claude Code (or another AI coding tool that supports skills/system prompts)
15+
- Open Brain MCP tools connected (`capture_thought`, `search_thoughts`)
16+
- Python 3.10+ installed
17+
- Gemini API key (free tier, get one at [ai.google.dev](https://ai.google.dev))
18+
19+
### Credential Tracker
20+
21+
```
22+
From your existing Open Brain setup:
23+
- Project URL: _______________
24+
- Open Brain MCP server connected: yes / no
25+
26+
New for this recipe:
27+
- Gemini API key: _______________ (from ai.google.dev, free tier works)
28+
```
29+
30+
## Steps
31+
32+
### 1. Create the skill directory
33+
34+
```bash
35+
mkdir -p ~/.claude/skills/infographic-generator
36+
```
37+
38+
### 2. Copy the skill and generation script
39+
40+
Copy `infographic-generator.skill.md` and `generate.py` from this recipe:
41+
42+
```bash
43+
cp infographic-generator.skill.md ~/.claude/skills/infographic-generator/SKILL.md
44+
cp generate.py ~/.claude/skills/infographic-generator/generate.py
45+
chmod +x ~/.claude/skills/infographic-generator/generate.py
46+
```
47+
48+
### 3. Set up the Python environment
49+
50+
```bash
51+
cd ~/.claude/skills/infographic-generator
52+
python3 -m venv .venv
53+
source .venv/bin/activate
54+
pip install google-genai Pillow
55+
```
56+
57+
### 4. Set your Gemini API key
58+
59+
```bash
60+
export GEMINI_API_KEY="your-api-key-here"
61+
```
62+
63+
Or add it to your shell profile for persistence:
64+
65+
```bash
66+
echo 'export GEMINI_API_KEY="your-api-key-here"' >> ~/.zshrc
67+
```
68+
69+
### 5. Verify Claude Code picks up the skill
70+
71+
Restart Claude Code. To verify, say "make an infographic from this doc" or "visualize this research" and confirm it references the Infographic Generator methodology.
72+
73+
### 6. Generate your first infographic
74+
75+
Point the skill at any markdown file:
76+
77+
```
78+
Make infographics from docs/research/my-analysis.md
79+
```
80+
81+
Or use Open Brain as the source:
82+
83+
```
84+
Visualize my thoughts about [topic]
85+
```
86+
87+
The skill will:
88+
1. Read and analyze the source content
89+
2. Chunk into logical segments (one infographic per major topic)
90+
3. Write verbose prompts with specific hex colors, layout types, and typography
91+
4. Save prompts to `./infographic-prompts/`
92+
5. Generate PNG images via Gemini API to `./media/`
93+
94+
### 7. Review and iterate
95+
96+
After generation, the skill shows each image. You can:
97+
98+
```
99+
redo 3 # regenerate infographic #3
100+
redo 3 premium # regenerate with higher quality model
101+
```
102+
103+
### 8. (Optional) Capture prompts to Open Brain
104+
105+
The generated prompts are 300+ word rich text documents. Storing them in Open Brain makes them searchable across sessions:
106+
107+
```
108+
Save these infographic prompts to Open Brain
109+
```
110+
111+
Future sessions can find them: `search_thoughts("infographic ROI analysis")`
112+
113+
## Expected Outcome
114+
115+
When working correctly, you should see:
116+
117+
- A prompt file saved to `./infographic-prompts/[source]-prompts.md` with one verbose prompt per content segment
118+
- PNG images saved to `./media/` with descriptive filenames
119+
- A manifest file at `./media/_latest_generation.json` linking filenames to titles
120+
- Each prompt includes specific hex colors, aspect ratio, layout type, typography specs, and "what to avoid" guidance
121+
122+
A typical 2-page research doc yields 4-8 infographics. Generation takes 30-60 seconds per image on Gemini's free tier.
123+
124+
## Open Brain Integration
125+
126+
This recipe connects to Open Brain in two ways:
127+
128+
**As a source (read side):** Search Open Brain for thoughts on a topic, synthesize them, then generate infographics from the synthesis. "Visualize my thoughts about MSP operations" pulls relevant thoughts and creates visuals from them.
129+
130+
**As storage (write side):** Generated prompts can be captured back to Open Brain. This creates a searchable library of visual templates. "What infographic did I make for the deal structure?" returns the prompt, which can be regenerated or adapted for new content.
131+
132+
Together with [Panning for Gold](../panning-for-gold/) and Auto-Capture Protocol (PR [#42](https://github.com/NateBJones-Projects/OB1/pull/42)), this forms a complete capture-process-visualize flywheel (see [Issue #84](https://github.com/NateBJones-Projects/OB1/issues/84)).
133+
134+
## Troubleshooting
135+
136+
**Issue:** `GEMINI_API_KEY not set` error when generating.
137+
**Solution:** Ensure the environment variable is exported in your current shell session. Run `echo $GEMINI_API_KEY` to verify. If using Claude Code, the shell inherits from your profile, so add it to `~/.zshrc` or `~/.bashrc`.
138+
139+
**Issue:** Gemini returns content policy errors for certain prompts.
140+
**Solution:** Some prompts trigger safety filters (surveillance themes, medical imagery, etc.). The script retries once automatically. If it still fails, the prompt is logged and skipped. You can manually edit the prompt to soften the language and use `--redo N` to regenerate just that image.
141+
142+
**Issue:** Images have poor text rendering.
143+
**Solution:** Gemini's free tier (Flash) struggles with small text in infographics. Use `--premium` flag for better text rendering (uses a higher-quality model, may require a paid API key depending on your Gemini plan).
144+
145+
**Issue:** Too many infographics generated from a short document.
146+
**Solution:** The skill chunks aggressively by default (one per section/topic). For shorter docs, tell the skill: "Make 3 infographics max from this doc" to constrain output.
147+
148+
---
149+
150+
*Part of the [Open Brain Flywheel](https://github.com/NateBJones-Projects/OB1/issues/84): capture, process, visualize.*
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Infographic image generator using Google Gemini API.
4+
Called by the infographic-generator skill after prompts are written.
5+
6+
Usage:
7+
python3 generate.py <prompts-file.md> [--output-dir ./media] [--premium] [--redo N]
8+
9+
Environment:
10+
GEMINI_API_KEY - Required. Get a free key at https://ai.google.dev
11+
12+
Flags:
13+
--premium Use higher-quality model (may require paid API key)
14+
--redo N Regenerate only infographic #N from the file
15+
"""
16+
17+
import sys
18+
import os
19+
import re
20+
import argparse
21+
from pathlib import Path
22+
23+
# Use the skill's venv if available
24+
VENV_SITE = Path(__file__).parent / ".venv" / "lib"
25+
for p in VENV_SITE.glob("python*/site-packages"):
26+
if str(p) not in sys.path:
27+
sys.path.insert(0, str(p))
28+
29+
from google import genai
30+
from google.genai import types
31+
32+
API_KEY = os.environ.get("GEMINI_API_KEY", "")
33+
if not API_KEY:
34+
print("ERROR: GEMINI_API_KEY environment variable is not set.")
35+
print("Get a free key at https://ai.google.dev")
36+
sys.exit(1)
37+
38+
MODEL_FREE = "gemini-2.5-flash-image"
39+
MODEL_PREMIUM = "gemini-3.1-flash-image-preview"
40+
41+
42+
def extract_prompts(md_path):
43+
"""Extract prompt blocks from the markdown file."""
44+
text = Path(md_path).read_text()
45+
46+
# Split on "## Infographic" headers
47+
sections = re.split(r"(?=## Infographic \d+)", text)
48+
prompts = []
49+
50+
for section in sections:
51+
# Find the title
52+
title_match = re.match(r'## Infographic \d+:\s*["\']?(.+?)["\']?\s*$', section, re.MULTILINE)
53+
if not title_match:
54+
continue
55+
title = title_match.group(1).strip().strip('"\'')
56+
57+
# Find the prompt content (everything after "### Prompt" line)
58+
prompt_match = re.search(r'### Prompt.*?\n\n(.+?)(?=\n---|\n## |\Z)', section, re.DOTALL)
59+
if not prompt_match:
60+
continue
61+
62+
prompt_text = prompt_match.group(1).strip()
63+
64+
# Extract aspect ratio if specified
65+
ar_match = re.search(r'Aspect ratio:\s*(\d+:\d+)', section)
66+
aspect_ratio = ar_match.group(1) if ar_match else "4:5"
67+
68+
# Clean the title for filename
69+
safe_name = re.sub(r'[^\w\s-]', '', title.lower())
70+
safe_name = re.sub(r'[\s]+', '-', safe_name).strip('-')[:60]
71+
72+
prompts.append({
73+
"title": title,
74+
"prompt": prompt_text,
75+
"aspect_ratio": aspect_ratio,
76+
"filename": safe_name + ".png",
77+
})
78+
79+
return prompts
80+
81+
82+
def generate_image(client, model, prompt_data):
83+
"""Generate a single infographic image."""
84+
response = client.models.generate_content(
85+
model=model,
86+
contents=prompt_data["prompt"],
87+
config=types.GenerateContentConfig(
88+
response_modalities=["IMAGE"],
89+
image_config=types.ImageConfig(
90+
aspect_ratio=prompt_data["aspect_ratio"],
91+
),
92+
),
93+
)
94+
95+
for part in response.parts:
96+
if part.inline_data:
97+
return part.as_image()
98+
99+
return None
100+
101+
102+
def main():
103+
parser = argparse.ArgumentParser(description="Generate infographic images from prompt markdown")
104+
parser.add_argument("prompts_file", help="Path to the prompts markdown file")
105+
parser.add_argument("--output-dir", default="./media", help="Output directory for images")
106+
parser.add_argument("--premium", action="store_true", help="Use higher-quality model (may require paid key)")
107+
parser.add_argument("--redo", type=int, help="Regenerate only infographic #N")
108+
args = parser.parse_args()
109+
110+
model = MODEL_PREMIUM if args.premium else MODEL_FREE
111+
output_dir = Path(args.output_dir)
112+
output_dir.mkdir(parents=True, exist_ok=True)
113+
114+
print(f"Model: {model} ({'premium' if args.premium else 'free tier'})")
115+
print(f"Output: {output_dir}")
116+
117+
prompts = extract_prompts(args.prompts_file)
118+
if not prompts:
119+
print("ERROR: No prompts found in file.")
120+
print("Expected format: '## Infographic N: Title' followed by '### Prompt' section")
121+
sys.exit(1)
122+
123+
print(f"Found {len(prompts)} prompts")
124+
125+
# Filter to single prompt if --redo
126+
if args.redo:
127+
idx = args.redo - 1
128+
if idx < 0 or idx >= len(prompts):
129+
print(f"ERROR: --redo {args.redo} out of range (1-{len(prompts)})")
130+
sys.exit(1)
131+
prompts = [prompts[idx]]
132+
print(f"Regenerating only: #{args.redo} - {prompts[0]['title']}")
133+
134+
client = genai.Client(api_key=API_KEY)
135+
136+
results = []
137+
for i, p in enumerate(prompts):
138+
num = args.redo if args.redo else i + 1
139+
progress_num = i + 1
140+
print(f"\n[{progress_num}/{len(prompts)}] Generating: {p['title']}")
141+
print(f" Aspect ratio: {p['aspect_ratio']}")
142+
143+
try:
144+
image = generate_image(client, model, p)
145+
if image:
146+
out_path = output_dir / p["filename"]
147+
image.save(str(out_path))
148+
print(f" SAVED: {out_path}")
149+
results.append({"file": p["filename"], "caption": p["title"], "path": str(out_path)})
150+
else:
151+
print(f" FAILED: No image in response")
152+
except Exception as e:
153+
print(f" ERROR: {e}")
154+
155+
# Print summary
156+
print(f"\n{'='*50}")
157+
print(f"Generated {len(results)}/{len(prompts)} images")
158+
for r in results:
159+
print(f" {r['file']}")
160+
161+
# Output JSON manifest for the skill to consume
162+
if results:
163+
import json
164+
manifest_path = output_dir / "_latest_generation.json"
165+
with open(manifest_path, "w") as f:
166+
json.dump(results, f, indent=2)
167+
print(f"\nManifest: {manifest_path}")
168+
169+
170+
if __name__ == "__main__":
171+
main()

0 commit comments

Comments
 (0)