Illustrator Agent
| File | lib/agents/illustratorAgent.ts |
| Triggered by | app/api/generate-image/route.ts |
| Image model | gemini-2.5-flash-image (Vertex AI, via lib/gcp/imagen.ts) |
| Storage | Google Cloud Storage (via lib/gcp/storage.ts) |
What it does
Generates one watercolor-style illustration per story page and uploads it to Google Cloud Storage. Returns a signed GCS URL for the frontend to display.
This agent does NOT use ADK (LlmAgent / FunctionTool). It is a plain async function that calls generateImage() and uploadAndSign() directly — no LLM routing layer. This eliminates ~3–5s of ADK overhead per page.
Decision logic
Input received
│
├── preGeneratedBase64 provided?
│ YES → decode base64 → uploadAndSign()
│
└── NO → generateImage(imagePrompt, referenceImages) → uploadAndSign()Storage path
images/{storyId}/page-{pageNumber}.pngUploaded to Google Cloud Storage and returned as a signed URL valid for public access.
Character reference images
When a user uploads a photo (e.g. a photo of their child), it is passed as referenceImages to generateImage(). The prompt instructs Gemini to preserve face shape, hair color, hair style, and skin tone while rendering in watercolor cartoon style.
referenceImages: [{ name: "Mia", base64: "..." }];Image style
All prompts are passed to generateImage() with a watercolor children's book illustration style suffix. The imagePrompt from StoryWriter is always in English regardless of the story language.
Rate limiting & retry
gemini-2.5-flash-image has a 2 RPM quota on Vertex AI. Pages are generated sequentially with a 32s start-to-start gap enforced by GenerationProvider.tsx and useStoryAssembler.ts. Running 2 in parallel exhausts the quota immediately and triggers exponential backoff (3s→5s→9s→17s→33s).
Inside generateImage() there are 3 progressive strategies:
| Strategy | Description |
|---|---|
original | Full prompt + reference images. Up to 5 retries on 429. |
scene-with-refs | Scene-only prompt (characters stripped) + reference images. Up to 3 retries. |
scene-only | Scene-only prompt, no reference images. Up to 3 retries. |
Each 429 retry uses exponential backoff: 2^attempt × 2000ms + random jitter. If a strategy gets IMAGE_PROHIBITED_CONTENT, it moves to the next strategy immediately without retrying.
Input / Output
// Input
{
imagePrompt: string; // English description from StoryWriter
storyId: string; // Firestore story document ID
pageNumber: number; // 1-based
referenceImages?: { name: string; base64: string }[];
preGeneratedBase64?: string;
}
// Output
{
imageUrl: string | null; // GCS signed URL, or null if failed
pageNumber: number;
error?: string;
}