"""Generate backnforth manual illustrations via Gemini image API (nano banana).""" import base64 import json import os import sys import time import urllib.request # key from disk, never printed KEY = os.environ.get("GEMINI_API_KEY") if not KEY: sys.exit('set GEMINI_API_KEY in the environment first') MODELS = ['gemini-3-pro-image', 'gemini-3.1-flash-image', 'gemini-2.5-flash-image'] STYLE = ("Flat vector cartoon illustration in a warm, friendly picture-book style. " "Soft rounded shapes, thick clean outlines, muted palette of deep teal, warm coral, " "cream and soft mustard yellow. Cozy, calm, reassuring mood. Simple, uncluttered " "composition on a plain cream background. Absolutely no text, no letters, no numbers, " "no words anywhere in the image. Landscape orientation. ") SCENES = { '01-cover': "A friendly therapist and a relaxed patient sitting in comfy armchairs in a cozy " "counselling room with a potted plant and a soft floor lamp. On a low table between " "them, an open laptop shows a dark screen with a single glowing green dot.", '02-open': "Close-up of gentle hands opening a slightly-worn laptop on a wooden table. The screen " "shows a simple dark page with one large round green play button and a small green dot.", '03-link': "One hand passing an old smartphone to another person's open hand. The phone screen " "glows dark with a single bright green dot. In the soft-focus background, a laptop " "shows the same green dot, suggesting the two screens are connected by a dotted arc.", '04-follow': "Side profile of a calm person's face with a serene expression, eyes looking to one " "side, following a small glowing green dot that trails a gentle curved motion path " "from the left side of the image to the right.", '05-adjust': "A hand with one finger adjusting a single very large horizontal slider on a dark " "tablet screen. At the left end of the slider sits a tiny cute cartoon turtle, at " "the right end a tiny cute cartoon rabbit.", '06-care': "A therapist leaning forward attentively with a kind expression, offering a glass of " "water to a patient who looks calm and relieved. The laptop on the side table is " "closed. Warm afternoon light through a window.", } OUT = os.path.expanduser('~/Documents/backnforth/img/manual') os.makedirs(OUT, exist_ok=True) def generate(prompt): body = json.dumps({'contents': [{'parts': [{'text': prompt}]}]}).encode() last = None for model in MODELS: url = ('https://generativelanguage.googleapis.com/v1beta/models/' '%s:generateContent?key=%s' % (model, KEY)) req = urllib.request.Request(url, data=body, headers={'Content-Type': 'application/json'}) for attempt in (1, 2): try: with urllib.request.urlopen(req, timeout=300) as r: data = json.load(r) for part in data['candidates'][0]['content']['parts']: if 'inlineData' in part: return base64.b64decode(part['inlineData']['data']), model last = 'no image part in response from %s' % model break # empty response: don't retry same model, fall through except Exception as e: last = '%s (try %d): %s' % (model, attempt, e) raise RuntimeError(last) for name, scene in SCENES.items(): path = os.path.join(OUT, name + '.png') if os.path.exists(path): print(name, 'exists, skipping') continue img, model = generate(STYLE + scene) open(path, 'wb').write(img) print('%s: %d KB via %s' % (name, len(img) // 1024, model)) time.sleep(2) print('done')