From 8602cc719c423f81220aabc8ee2c6f33a55e190d Mon Sep 17 00:00:00 2001 From: Jared Irish Date: Wed, 18 Mar 2026 23:07:42 -0400 Subject: [PATCH 1/4] [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 #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) --- recipes/infographic-generator/README.md | 150 ++++++++++++++ recipes/infographic-generator/generate.py | 189 ++++++++++++++++++ .../infographic-generator.skill.md | 144 +++++++++++++ recipes/infographic-generator/metadata.json | 20 ++ 4 files changed, 503 insertions(+) create mode 100644 recipes/infographic-generator/README.md create mode 100644 recipes/infographic-generator/generate.py create mode 100644 recipes/infographic-generator/infographic-generator.skill.md create mode 100644 recipes/infographic-generator/metadata.json diff --git a/recipes/infographic-generator/README.md b/recipes/infographic-generator/README.md new file mode 100644 index 000000000..dd57ded41 --- /dev/null +++ b/recipes/infographic-generator/README.md @@ -0,0 +1,150 @@ +# Infographic Generator + +*Turn research docs and Open Brain thoughts into professional infographic images* + +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. + +## What It Does + +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. + +## Prerequisites + +- Working Open Brain setup ([guide](../../docs/01-getting-started.md)) +- Claude Code (or another AI coding tool that supports skills/system prompts) +- Open Brain MCP tools connected (`capture_thought`, `search_thoughts`) +- Python 3.10+ installed +- Gemini API key (free tier, get one at [ai.google.dev](https://ai.google.dev)) + +### Credential Tracker + +``` +From your existing Open Brain setup: +- Project URL: _______________ +- Open Brain MCP server connected: yes / no + +New for this recipe: +- Gemini API key: _______________ (from ai.google.dev, free tier works) +``` + +## Steps + +### 1. Create the skill directory + +```bash +mkdir -p ~/.claude/skills/infographic-generator +``` + +### 2. Copy the skill and generation script + +Copy `infographic-generator.skill.md` and `generate.py` from this recipe: + +```bash +cp infographic-generator.skill.md ~/.claude/skills/infographic-generator/SKILL.md +cp generate.py ~/.claude/skills/infographic-generator/generate.py +chmod +x ~/.claude/skills/infographic-generator/generate.py +``` + +### 3. Set up the Python environment + +```bash +cd ~/.claude/skills/infographic-generator +python3 -m venv .venv +source .venv/bin/activate +pip install google-genai Pillow +``` + +### 4. Set your Gemini API key + +```bash +export GEMINI_API_KEY="your-api-key-here" +``` + +Or add it to your shell profile for persistence: + +```bash +echo 'export GEMINI_API_KEY="your-api-key-here"' >> ~/.zshrc +``` + +### 5. Verify Claude Code picks up the skill + +Restart Claude Code. To verify, say "make an infographic from this doc" or "visualize this research" and confirm it references the Infographic Generator methodology. + +### 6. Generate your first infographic + +Point the skill at any markdown file: + +``` +Make infographics from docs/research/my-analysis.md +``` + +Or use Open Brain as the source: + +``` +Visualize my thoughts about [topic] +``` + +The skill will: +1. Read and analyze the source content +2. Chunk into logical segments (one infographic per major topic) +3. Write verbose prompts with specific hex colors, layout types, and typography +4. Save prompts to `./infographic-prompts/` +5. Generate PNG images via Gemini API to `./media/` + +### 7. Review and iterate + +After generation, the skill shows each image. You can: + +``` +redo 3 # regenerate infographic #3 +redo 3 premium # regenerate with higher quality model +``` + +### 8. (Optional) Capture prompts to Open Brain + +The generated prompts are 300+ word rich text documents. Storing them in Open Brain makes them searchable across sessions: + +``` +Save these infographic prompts to Open Brain +``` + +Future sessions can find them: `search_thoughts("infographic ROI analysis")` + +## Expected Outcome + +When working correctly, you should see: + +- A prompt file saved to `./infographic-prompts/[source]-prompts.md` with one verbose prompt per content segment +- PNG images saved to `./media/` with descriptive filenames +- A manifest file at `./media/_latest_generation.json` linking filenames to titles +- Each prompt includes specific hex colors, aspect ratio, layout type, typography specs, and "what to avoid" guidance + +A typical 2-page research doc yields 4-8 infographics. Generation takes 30-60 seconds per image on Gemini's free tier. + +## Open Brain Integration + +This recipe connects to Open Brain in two ways: + +**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. + +**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. + +Together with [Panning for Gold](../brain-dump-processor/) and [Auto-Capture Protocol](../auto-capture-protocol/), this forms a complete capture-process-visualize flywheel (see [Issue #84](https://github.com/NateBJones-Projects/OB1/issues/84)). + +## Troubleshooting + +**Issue:** `GEMINI_API_KEY not set` error when generating. +**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`. + +**Issue:** Gemini returns content policy errors for certain prompts. +**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. + +**Issue:** Images have poor text rendering. +**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). + +**Issue:** Too many infographics generated from a short document. +**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. + +--- + +*Part of the [Open Brain Flywheel](https://github.com/NateBJones-Projects/OB1/issues/84): capture, process, visualize.* diff --git a/recipes/infographic-generator/generate.py b/recipes/infographic-generator/generate.py new file mode 100644 index 000000000..1cbc8a3d8 --- /dev/null +++ b/recipes/infographic-generator/generate.py @@ -0,0 +1,189 @@ +#!/usr/bin/env python3 +""" +Infographic image generator using Google Gemini API. +Called by the infographic-generator skill after prompts are written. + +Usage: + python3 generate.py [--output-dir ./media] [--premium] [--redo N] + +Environment: + GEMINI_API_KEY - Required. Get a free key at https://ai.google.dev + +Flags: + --premium Use higher-quality model (may require paid API key) + --redo N Regenerate only infographic #N from the file +""" + +import sys +import os +import re +import argparse +import json +from pathlib import Path + +# Use the skill's venv if available +VENV_SITE = Path(__file__).parent / ".venv" / "lib" +for p in VENV_SITE.glob("python*/site-packages"): + if str(p) not in sys.path: + sys.path.insert(0, str(p)) + +from google import genai +from google.genai import types + +API_KEY = os.environ.get("GEMINI_API_KEY", "") +if not API_KEY: + print("ERROR: GEMINI_API_KEY environment variable is not set.") + print("Get a free key at https://ai.google.dev") + sys.exit(1) + +MODEL_FREE = "gemini-2.5-flash-image" +MODEL_PREMIUM = "gemini-3.1-flash-image-preview" + + +def extract_prompts(md_path): + """Extract prompt blocks from the markdown file.""" + text = Path(md_path).read_text() + + # Split on "## Infographic" headers + sections = re.split(r"(?=## Infographic \d+)", text) + prompts = [] + + for section in sections: + # Find the title + title_match = re.match(r'## Infographic \d+:\s*["\']?(.+?)["\']?\s*$', section, re.MULTILINE) + if not title_match: + continue + title = title_match.group(1).strip().strip('"\'') + + # Find the prompt content (everything after "### Prompt" line) + prompt_match = re.search(r'### Prompt.*?\n\n(.+?)(?=\n---|\n## |\Z)', section, re.DOTALL) + if not prompt_match: + continue + + prompt_text = prompt_match.group(1).strip() + + # Extract aspect ratio if specified + ar_match = re.search(r'Aspect ratio:\s*(\d+:\d+)', section) + aspect_ratio = ar_match.group(1) if ar_match else "4:5" + + # Clean the title for filename + safe_name = re.sub(r'[^\w\s-]', '', title.lower()) + safe_name = re.sub(r'[\s]+', '-', safe_name).strip('-')[:60] + + prompts.append({ + "title": title, + "prompt": prompt_text, + "aspect_ratio": aspect_ratio, + "filename": safe_name + ".png", + }) + + return prompts + + +def generate_image(client, model, prompt_data): + """Generate a single infographic image.""" + try: + response = client.models.generate_content( + model=model, + contents=prompt_data["prompt"], + config=types.GenerateContentConfig( + response_modalities=["IMAGE"], + image_config=types.ImageConfig( + aspect_ratio=prompt_data["aspect_ratio"], + ), + ), + ) + + for part in response.parts: + if part.inline_data: + return part.as_image() + except Exception as e: + print(f" Generation error: {e}") + # Retry once + print(" Retrying...") + try: + response = client.models.generate_content( + model=model, + contents=prompt_data["prompt"], + config=types.GenerateContentConfig( + response_modalities=["IMAGE"], + image_config=types.ImageConfig( + aspect_ratio=prompt_data["aspect_ratio"], + ), + ), + ) + for part in response.parts: + if part.inline_data: + return part.as_image() + except Exception as retry_error: + print(f" Retry failed: {retry_error}") + + return None + + +def main(): + parser = argparse.ArgumentParser(description="Generate infographic images from prompt markdown") + parser.add_argument("prompts_file", help="Path to the prompts markdown file") + parser.add_argument("--output-dir", default="./media", help="Output directory for images") + parser.add_argument("--premium", action="store_true", help="Use higher-quality model (may require paid key)") + parser.add_argument("--redo", type=int, help="Regenerate only infographic #N") + args = parser.parse_args() + + model = MODEL_PREMIUM if args.premium else MODEL_FREE + output_dir = Path(args.output_dir) + output_dir.mkdir(parents=True, exist_ok=True) + + print(f"Model: {model} ({'premium' if args.premium else 'free tier'})") + print(f"Output: {output_dir}") + + prompts = extract_prompts(args.prompts_file) + if not prompts: + print("ERROR: No prompts found in file.") + print("Expected format: '## Infographic N: Title' followed by '### Prompt' section") + sys.exit(1) + + print(f"Found {len(prompts)} prompts") + + # Filter to single prompt if --redo + if args.redo: + idx = args.redo - 1 + if idx < 0 or idx >= len(prompts): + print(f"ERROR: --redo {args.redo} out of range (1-{len(prompts)})") + sys.exit(1) + prompts = [prompts[idx]] + print(f"Regenerating only: #{args.redo} - {prompts[0]['title']}") + + client = genai.Client(api_key=API_KEY) + + results = [] + for i, p in enumerate(prompts): + num = args.redo if args.redo else i + 1 + total = len(prompts) if not args.redo else args.redo + print(f"\n[{num}/{total}] Generating: {p['title']}") + print(f" Aspect ratio: {p['aspect_ratio']}") + + image = generate_image(client, model, p) + if image: + out_path = output_dir / p["filename"] + image.save(str(out_path)) + print(f" SAVED: {out_path}") + results.append({"file": p["filename"], "caption": p["title"], "path": str(out_path)}) + else: + print(f" FAILED: No image generated") + + # Print summary + print(f"\n{'='*50}") + print(f"Generated {len(results)}/{len(prompts)} images") + for r in results: + print(f" {r['file']}") + + # Output JSON manifest for the skill to consume + if results: + manifest_path = output_dir / "_latest_generation.json" + with open(manifest_path, "w") as f: + json.dump(results, f, indent=2) + print(f"\nManifest: {manifest_path}") + + +if __name__ == "__main__": + main() diff --git a/recipes/infographic-generator/infographic-generator.skill.md b/recipes/infographic-generator/infographic-generator.skill.md new file mode 100644 index 000000000..88195c556 --- /dev/null +++ b/recipes/infographic-generator/infographic-generator.skill.md @@ -0,0 +1,144 @@ +--- +name: infographic-generator +description: Generate infographic images from any research doc, Open Brain thoughts, or analysis. Auto-chunks content, writes prompts, generates images via Gemini API (free tier), and saves to media/. Use --premium for better text rendering. +--- + +# Infographic Generator + +Turn research, analysis, or documentation into infographic images. Generates verbose prompts, then calls the Gemini API to create the images automatically. + +## Commands + +| Invocation | What it does | +|-----------|-------------| +| `infographic docs/strategy/deal.md` | Full pipeline: read doc, write prompts, generate images, save to media/ | +| `infographic current` | Use recent conversation content as source | +| `infographic --generate-only infographic-prompts/deal.md` | Skip prompt writing, just generate images from existing prompt file | +| `infographic --premium docs/report.md` | Use higher-quality model (better text rendering) | +| `infographic --redo 3 infographic-prompts/deal.md` | Regenerate just infographic #3 from existing prompts | + +## Shorthand triggers + +These also activate this skill: +- "infographic", "image prompt", "visualize this", "make this visual", "turn into a graphic" +- "generate the infographics", "make the images" +- "redo that one", "fix that image", "regenerate #3" + +## Process + +### Step 1: Route the request + +Check the arguments: +- If `--generate-only ` or `--redo N `: skip to Step 6 (generation only) +- If a file path is provided: read that file as source content +- If "current": use the most recent research/analysis from the conversation +- If no argument: ask the user what content to convert + +### Step 2: Identify the audience + +Check for audience keywords. Default: business professional. + +| Keyword | Calibration | +|---------|------------| +| Family/non-technical | Simplify heavily. No jargon. Warm tone. | +| Industry peers | Industry-aware. Technical terms are fine. Business model clarity. | +| C-suite/prospects | ROI-focused. Numbers, percentages, before/after. | +| LinkedIn/social | Hook-driven. One big insight per graphic. Bold headline. | +| Technical | Full detail. Architecture diagrams, competitive landscapes, financial models. | + +### Step 3: Analyze and chunk the content + +Read the source and identify logical segments. Each segment = one infographic prompt. +- One infographic per major topic or section +- Maximum 5-7 data points per graphic +- Tables, timelines, hierarchies, and flows each get their own graphic +- If content would make 8+ graphics, split into two files by theme + +### Step 4: Generate verbose prompts + +For EACH segment, write a prompt block following this structure: + +``` +## Infographic [N]: [Title] +**Audience:** [who] + +### Prompt (copy everything below this line) + +Create a professional infographic with the following specifications: + +**Subject:** [one-sentence summary] + +**Key Data Points:** +- [bullet 1 with specific numbers] +- [bullet 2-7 max] + +**Visual Layout:** +- [layout type: flow chart, comparison, timeline, pyramid, etc.] +- [spatial arrangement] +- Aspect ratio: [16:9 for landscape, 4:5 for phone, 1:1 for general, 9:16 for tall] + +**Design Style:** +- [specific hex colors, not just "blue"] +- [background, corner style, icon style] + +**Typography:** +- Headline: [max 8 words] +- Subheadline: [the "so what"] +- Data labels: small but readable +- No paragraphs. Bullets and numbers only. + +**What to emphasize:** +- [the single most important takeaway] + +**What to avoid:** +- Stock photos, clip art, cheesy business imagery +- More than 3 colors plus neutrals +- Tiny text or overcrowded layouts +- Generic AI aesthetic (gradients, floating cubes, robot hands) +``` + +### Step 5: Save prompts + +Write all prompts to `./infographic-prompts/[source-filename]-prompts.md`. Create the directory if needed. Display in conversation too. + +### Step 6: Generate images + +Run the generation script: + +```bash +python3 ~/.claude/skills/infographic-generator/generate.py \ + "[prompts-file-path]" \ + --output-dir "./media" \ + [--premium] \ + [--redo N] +``` + +After generation: +1. Read the manifest file (`media/_latest_generation.json`) to see what was created +2. Display each generated image to the user using the Read tool so they can review +3. Ask: "Any of these need a redo? Say 'redo 3' or 'redo 3 premium' to regenerate with better quality." + +### Step 7: Handle redo requests + +When user says "redo N" or "fix that one" or "redo N premium": +- Parse which infographic number to redo +- If "premium" mentioned, add --premium flag +- Run generation script with --redo N +- Show the new result +- Ask again if they want more changes + +## Important rules + +- Keep prompts verbose (300+ words each). More detail = better images. +- Always include specific hex colors, not just "blue" or "green." +- Always specify aspect ratio. +- Always mention what to AVOID (dramatically improves output). +- If content has numbers, ALWAYS include them. Numbers make infographics credible. +- Each prompt is independently useful (don't reference other prompts). +- Default to FREE model. Only use premium when user explicitly asks or when redoing a specific image. + +## Open Brain Integration + +**As source:** Use `search_thoughts` to find content on a topic, synthesize it, then generate infographics from the synthesis. + +**As storage:** After generation, capture the prompts to Open Brain with `capture_thought`. This creates a searchable library of visual templates that can be regenerated or adapted in future sessions. diff --git a/recipes/infographic-generator/metadata.json b/recipes/infographic-generator/metadata.json new file mode 100644 index 000000000..ab7e49525 --- /dev/null +++ b/recipes/infographic-generator/metadata.json @@ -0,0 +1,20 @@ +{ + "name": "Infographic Generator", + "description": "Turn research docs, Open Brain thoughts, and analysis into professional infographic images via Gemini's free-tier image generation API.", + "category": "recipes", + "author": { + "name": "Jared Irish", + "github": "jaredirish" + }, + "version": "1.0.0", + "requires": { + "open_brain": true, + "services": ["Gemini API (free tier)"], + "tools": ["Claude Code or similar AI coding tool", "Python 3.10+"] + }, + "tags": ["infographic", "visualization", "image-generation", "gemini", "research", "content"], + "difficulty": "intermediate", + "estimated_time": "15 minutes", + "created": "2026-03-18", + "updated": "2026-03-18" +} From 3ecea60de6e5da2c411dbc8faf7fda9bf4c49c47 Mon Sep 17 00:00:00 2001 From: Jared Irish Date: Mon, 30 Mar 2026 21:18:58 -0400 Subject: [PATCH 2/4] [recipes] Fix broken relative links in infographic-generator README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ../brain-dump-processor/ → ../panning-for-gold/ ../auto-capture-protocol/ → ../auto-capture/ Co-Authored-By: Claude Opus 4.6 (1M context) --- recipes/infographic-generator/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/infographic-generator/README.md b/recipes/infographic-generator/README.md index dd57ded41..ebb9c2d34 100644 --- a/recipes/infographic-generator/README.md +++ b/recipes/infographic-generator/README.md @@ -129,7 +129,7 @@ This recipe connects to Open Brain in two ways: **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. -Together with [Panning for Gold](../brain-dump-processor/) and [Auto-Capture Protocol](../auto-capture-protocol/), this forms a complete capture-process-visualize flywheel (see [Issue #84](https://github.com/NateBJones-Projects/OB1/issues/84)). +Together with [Panning for Gold](../panning-for-gold/) and [Auto-Capture Protocol](../auto-capture/), this forms a complete capture-process-visualize flywheel (see [Issue #84](https://github.com/NateBJones-Projects/OB1/issues/84)). ## Troubleshooting From fe5a906571a2d783bc6989dd08067b575f71755a Mon Sep 17 00:00:00 2001 From: Jared Irish Date: Mon, 30 Mar 2026 21:29:22 -0400 Subject: [PATCH 3/4] [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 #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) --- recipes/infographic-generator/README.md | 2 +- recipes/infographic-generator/generate.py | 71 +++++++++-------------- 2 files changed, 27 insertions(+), 46 deletions(-) diff --git a/recipes/infographic-generator/README.md b/recipes/infographic-generator/README.md index ebb9c2d34..4dab41177 100644 --- a/recipes/infographic-generator/README.md +++ b/recipes/infographic-generator/README.md @@ -129,7 +129,7 @@ This recipe connects to Open Brain in two ways: **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. -Together with [Panning for Gold](../panning-for-gold/) and [Auto-Capture Protocol](../auto-capture/), this forms a complete capture-process-visualize flywheel (see [Issue #84](https://github.com/NateBJones-Projects/OB1/issues/84)). +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)). ## Troubleshooting diff --git a/recipes/infographic-generator/generate.py b/recipes/infographic-generator/generate.py index 1cbc8a3d8..273649d0e 100644 --- a/recipes/infographic-generator/generate.py +++ b/recipes/infographic-generator/generate.py @@ -18,7 +18,6 @@ import os import re import argparse -import json from pathlib import Path # Use the skill's venv if available @@ -82,41 +81,20 @@ def extract_prompts(md_path): def generate_image(client, model, prompt_data): """Generate a single infographic image.""" - try: - response = client.models.generate_content( - model=model, - contents=prompt_data["prompt"], - config=types.GenerateContentConfig( - response_modalities=["IMAGE"], - image_config=types.ImageConfig( - aspect_ratio=prompt_data["aspect_ratio"], - ), + response = client.models.generate_content( + model=model, + contents=prompt_data["prompt"], + config=types.GenerateContentConfig( + response_modalities=["IMAGE"], + image_config=types.ImageConfig( + aspect_ratio=prompt_data["aspect_ratio"], ), - ) - - for part in response.parts: - if part.inline_data: - return part.as_image() - except Exception as e: - print(f" Generation error: {e}") - # Retry once - print(" Retrying...") - try: - response = client.models.generate_content( - model=model, - contents=prompt_data["prompt"], - config=types.GenerateContentConfig( - response_modalities=["IMAGE"], - image_config=types.ImageConfig( - aspect_ratio=prompt_data["aspect_ratio"], - ), - ), - ) - for part in response.parts: - if part.inline_data: - return part.as_image() - except Exception as retry_error: - print(f" Retry failed: {retry_error}") + ), + ) + + for part in response.parts: + if part.inline_data: + return part.as_image() return None @@ -158,18 +136,20 @@ def main(): results = [] for i, p in enumerate(prompts): num = args.redo if args.redo else i + 1 - total = len(prompts) if not args.redo else args.redo - print(f"\n[{num}/{total}] Generating: {p['title']}") + print(f"\n[{num}/{len(prompts)}] Generating: {p['title']}") print(f" Aspect ratio: {p['aspect_ratio']}") - image = generate_image(client, model, p) - if image: - out_path = output_dir / p["filename"] - image.save(str(out_path)) - print(f" SAVED: {out_path}") - results.append({"file": p["filename"], "caption": p["title"], "path": str(out_path)}) - else: - print(f" FAILED: No image generated") + try: + image = generate_image(client, model, p) + if image: + out_path = output_dir / p["filename"] + image.save(str(out_path)) + print(f" SAVED: {out_path}") + results.append({"file": p["filename"], "caption": p["title"], "path": str(out_path)}) + else: + print(f" FAILED: No image in response") + except Exception as e: + print(f" ERROR: {e}") # Print summary print(f"\n{'='*50}") @@ -179,6 +159,7 @@ def main(): # Output JSON manifest for the skill to consume if results: + import json manifest_path = output_dir / "_latest_generation.json" with open(manifest_path, "w") as f: json.dump(results, f, indent=2) From 406b42f79e7852b479adde72c0b8dee45998637e Mon Sep 17 00:00:00 2001 From: Jonathan Edwards Date: Sun, 12 Apr 2026 22:11:19 -0400 Subject: [PATCH 4/4] [recipes] Fix infographic redo progress output --- recipes/infographic-generator/generate.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/recipes/infographic-generator/generate.py b/recipes/infographic-generator/generate.py index 273649d0e..c16eb1ebf 100644 --- a/recipes/infographic-generator/generate.py +++ b/recipes/infographic-generator/generate.py @@ -136,7 +136,8 @@ def main(): results = [] for i, p in enumerate(prompts): num = args.redo if args.redo else i + 1 - print(f"\n[{num}/{len(prompts)}] Generating: {p['title']}") + progress_num = i + 1 + print(f"\n[{progress_num}/{len(prompts)}] Generating: {p['title']}") print(f" Aspect ratio: {p['aspect_ratio']}") try: