Skip to content

AI Generation API

Genkit flows for script analysis and SVG asset generation.


Overview

On Book Pro uses Google Genkit with Gemini Pro for:

  • Script Analysis — Extract scenes, characters, and props from PDF scripts
  • SVG Asset Generation — AI-generated stage furniture icons

Architecture

┌─────────────────┐
│   Client App    │
└────────┬────────┘
         │ httpsCallable

┌─────────────────┐
│   Firebase      │
│   Functions     │
└────────┬────────┘


┌─────────────────┐      ┌─────────────────┐
│   Genkit Flow   │─────►│   Gemini Pro    │
└─────────────────┘      └─────────────────┘

Script Analysis

Client Request

typescript
import { httpsCallable } from 'firebase/functions';

export const analyzeScript = async (pdfBase64: string) => {
    const analyze = httpsCallable(functions, 'analyzeScript');
    
    const result = await analyze({ pdfContent: pdfBase64 });
    
    return result.data as ScriptAnalysisResult;
};

Genkit Flow Definition

typescript
// functions/src/ai/analyze-script.ts
import { defineFlow, run } from '@genkit-ai/flow';
import { gemini15Pro } from '@genkit-ai/googleai';

export const analyzeScriptFlow = defineFlow(
    {
        name: 'analyzeScript',
        inputSchema: z.object({
            pdfContent: z.string(),
        }),
        outputSchema: z.object({
            scenes: z.array(SceneSchema),
            characters: z.array(CharacterSchema),
            props: z.array(PropSchema),
        }),
    },
    async (input) => {
        const { pdfContent } = input;
        
        // Extract text from PDF
        const text = await extractPdfText(pdfContent);
        
        // Call Gemini for analysis
        const result = await run('gemini-analysis', async () => {
            return gemini15Pro.generate({
                prompt: buildAnalysisPrompt(text),
                config: {
                    temperature: 0.2,
                    maxOutputTokens: 8192,
                },
            });
        });
        
        return parseAnalysisResponse(result.text);
    }
);

Analysis Prompt Pattern

typescript
const buildAnalysisPrompt = (scriptText: string): string => `
You are a theatrical script analyst. Analyze the following script and extract:

1. **Scenes**: Act/Scene numbers, locations, and brief descriptions
2. **Characters**: Names, descriptions, and notable traits
3. **Props**: Physical objects mentioned in stage directions

Format your response as JSON matching this schema:
{
  "scenes": [{"act": number, "scene": number, "location": string, "description": string}],
  "characters": [{"name": string, "description": string}],
  "props": [{"name": string, "firstAppearance": string}]
}

SCRIPT:
${scriptText}
`;

SVG Asset Generation

Client Request

typescript
export const generateStageFurniture = async (description: string) => {
    const generate = httpsCallable(functions, 'generateStageFurnitureIcon');
    
    const result = await generate({ description });
    
    return result.data as { svg: string };
};

Genkit Flow

typescript
// functions/src/ai/generate-svg.ts
export const generateSvgFlow = defineFlow(
    {
        name: 'generateStageFurnitureIcon',
        inputSchema: z.object({
            description: z.string(),
        }),
        outputSchema: z.object({
            svg: z.string(),
        }),
    },
    async (input) => {
        const result = await gemini15Pro.generate({
            prompt: buildSvgPrompt(input.description),
            config: {
                temperature: 0.7,
            },
        });
        
        // Extract SVG from response
        const svg = extractSvgFromResponse(result.text);
        
        // Validate SVG
        if (!isValidSvg(svg)) {
            throw new Error('Invalid SVG generated');
        }
        
        return { svg };
    }
);

SVG Prompt Pattern

typescript
const buildSvgPrompt = (description: string): string => `
Generate a simple, monochrome SVG icon for a piece of stage furniture.

REQUIREMENTS:
- Simple, flat design suitable for a technical drawing
- Single color (use currentColor for flexibility)
- ViewBox: 0 0 48 48
- No gradients or complex effects
- Stage manager-readable at small sizes

DESCRIPTION: ${description}

Return ONLY the SVG code, no explanation.
`;

Error Handling

Retry Logic

typescript
const withRetry = async <T>(
    fn: () => Promise<T>,
    maxRetries: number = 3
): Promise<T> => {
    let lastError: Error | undefined;
    
    for (let attempt = 0; attempt < maxRetries; attempt++) {
        try {
            return await fn();
        } catch (error) {
            lastError = error as Error;
            
            // Don't retry on validation errors
            if (error.message.includes('Invalid')) {
                throw error;
            }
            
            // Exponential backoff
            await new Promise(r => setTimeout(r, Math.pow(2, attempt) * 1000));
        }
    }
    
    throw lastError;
};

Rate Limiting

typescript
import { RateLimiter } from '@/utils/rate-limiter';

const aiLimiter = new RateLimiter({
    maxRequests: 10,
    windowMs: 60000, // 10 requests per minute per user
});

export const analyzeScriptWithLimit = onCall(async (request) => {
    const userId = request.auth?.uid;
    
    if (!aiLimiter.check(userId)) {
        throw new HttpsError('resource-exhausted', 'Rate limit exceeded');
    }
    
    return analyzeScriptFlow(request.data);
});

Response Schemas

Script Analysis Result

typescript
interface ScriptAnalysisResult {
    scenes: {
        act: number;
        scene: number;
        location: string;
        description: string;
    }[];
    characters: {
        name: string;
        description: string;
    }[];
    props: {
        name: string;
        firstAppearance: string;
    }[];
}

SVG Generation Result

typescript
interface SvgGenerationResult {
    svg: string;  // Raw SVG markup
}

Environment Variables

bash
# Required in Firebase Functions environment
GOOGLE_GENAI_API_KEY=your-gemini-api-key

Set secrets using Firebase CLI:

bash
firebase functions:secrets:set GOOGLE_GENAI_API_KEY

Barry AI Chat

Overview

The barryChat Cloud Function provides an AI production assistant powered by Gemini with tool-calling. It runs a multi-round conversation loop, executing tools to query project data before synthesizing a natural-language response.

Endpoint

typescript
// Client
const barryChat = httpsCallable<BarryChatRequest, BarryChatResponse>(
    functions, 'barryChat'
);

Request

typescript
interface BarryChatRequest {
    projectId: string;
    message: string;
    history?: Array<{ role: 'user' | 'model'; text: string }>;
}

Response

typescript
interface BarryChatResponse {
    response: string;    // Gemini's synthesized answer (markdown)
    toolsUsed: string[]; // Names of tools invoked (e.g., 'searchFiles', 'getProductionData')
}

Configuration

SettingValue
Regionus-central1
Timeout120s
Memory512 MiB
Max tool-call rounds5
AuthRequired (Firebase Auth)

Available Tools

ToolDescriptionData Source
searchFilesRAG over uploaded project files (scripts, notes, riders)Vertex AI Data Store
getProductionDataRead live production data (structure, cast, props, scheduler, etc.)Firestore shards

Files

  • functions/src/barry-chat.ts — Orchestrator Cloud Function
  • functions/src/barry-tools.ts — Tool execution functions
  • functions/src/barry-datastore.ts — Vertex AI Search Data Store integration
  • functions/src/barry-config.ts — Model and shard configuration
  • functions/src/barry-prompts.ts — System prompts and few-shot examples

Full Architecture →


Last updated: February 2026 (Barry Chat API added)