Provider-agnostic Text-to-Image middleware with GDPR compliance. Supports Google Cloud (Imagen, Gemini), Eden AI, and IONOS.
npm install @loonylabs/tti-middlewarebash
npm install @loonylabs/tti-middleware
For Google Cloud provider (recommended):
npm install @google-cloud/aiplatform @google/genai
`
Or install directly from GitHub:
`bash
npm install github:loonylabs-dev/tti-middleware
`
$3
`typescript
import { TTIService, GoogleCloudTTIProvider, TTIProvider } from '@loonylabs/tti-middleware';
// Create service and register provider
const service = new TTIService();
service.registerProvider(new GoogleCloudTTIProvider({
projectId: process.env.GOOGLE_CLOUD_PROJECT,
region: 'europe-west4', // EU region for GDPR
}));
// Generate an image
const result = await service.generate({
prompt: 'A futuristic city with flying cars, cyberpunk style',
model: 'imagen-3',
});
console.log('Image generated:', result.images[0].base64?.substring(0, 50) + '...');
console.log('Duration:', result.metadata.duration, 'ms');
`
Using Character Consistency
Generate consistent characters across multiple images:
`typescript
// 1. Create the initial character
const character = await service.generate({
prompt: 'A cute cartoon bear with a red hat and blue scarf, watercolor style',
model: 'gemini-flash-image', // Only this model supports character consistency!
});
// 2. Generate new scenes with the same character (Structured Mode)
const scene = await service.generate({
prompt: 'dancing happily in the rain, jumping in puddles',
model: 'gemini-flash-image',
referenceImages: [{
base64: character.images[0].base64!,
mimeType: 'image/png',
}],
subjectDescription: 'cute cartoon bear with red hat and blue scarf',
});
// 3. Or use Index-Based Mode for multiple characters
const multiCharScene = await service.generate({
prompt: 'The FIRST reference image character meets the SECOND reference image character',
model: 'gemini-flash-image',
referenceImages: [
{ base64: character1.images[0].base64!, mimeType: 'image/png' },
{ base64: character2.images[0].base64!, mimeType: 'image/png' },
],
// subjectDescription omitted = Index-Based Mode
});
`
Important: Character consistency is only supported by gemini-flash-image model!
Switching Providers
`typescript
// Use Google Cloud (recommended for EU)
const googleResult = await service.generate({
prompt: 'A mountain landscape',
model: 'imagen-3',
}, TTIProvider.GOOGLE_CLOUD);
// Use Eden AI (experimental)
const edenResult = await service.generate({
prompt: 'A mountain landscape',
model: 'openai', // Uses DALL-E via Eden AI
}, TTIProvider.EDENAI);
// Use IONOS (experimental)
const ionosResult = await service.generate({
prompt: 'A mountain landscape',
}, TTIProvider.IONOS);
`
Prerequisites
Required Dependencies
- Node.js 18+
- TypeScript 5.3+
- Google Cloud SDK (optional, for Google Cloud provider)
For Google Cloud provider:
`bash
npm install @google-cloud/aiplatform @google/genai
`
Configuration
Environment Setup
Create a .env file in your project root:
`env
Default provider
TTI_DEFAULT_PROVIDER=google-cloud
Logging level (debug, info, warn, error, silent)
TTI_LOG_LEVEL=info
Google Cloud (recommended for EU/GDPR)
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_APPLICATION_CREDENTIALS=./service-account.json
GOOGLE_CLOUD_REGION=europe-west4 # Recommended for Gemini
Eden AI (experimental)
EDENAI_API_KEY=your-api-key
IONOS (experimental)
IONOS_API_KEY=your-api-key
IONOS_API_URL=https://api.ionos.cloud/ai/v1
`
Providers & Models
$3
| Model | ID | Character Consistency | EU Regions |
|-------|-----|----------------------|------------|
| Imagen 3 | imagen-3 | No | All EU regions |
| Gemini Flash Image | gemini-flash-image | Yes | europe-west1, europe-west4, europe-north1 |
Important: gemini-flash-image is NOT available in europe-west3 (Frankfurt)!
$3
| Model | ID | Notes |
|-------|-----|-------|
| OpenAI DALL-E | openai | Via Eden AI aggregator |
| Stability AI | stabilityai | Via Eden AI aggregator |
| Replicate | replicate | Via Eden AI aggregator |
$3
| Model | ID | Notes |
|-------|-----|-------|
| Default | default | OpenAI-compatible API |
$3
| Region | Location | Imagen 3 | Gemini Flash Image |
|--------|----------|----------|-------------------|
| europe-west1 | Belgium | Yes | Yes |
| europe-west3 | Frankfurt | Yes | No |
| europe-west4 | Netherlands | Yes | Yes (Recommended) |
| europe-north1 | Finland | Yes | Yes |
| europe-west9 | Paris | Yes | No |
Character Consistency
Generate consistent characters across multiple images - perfect for children's book illustrations.
$3
Best for scenes with a single consistent character:
`typescript
// Step 1: Create a character
const bear = await service.generate({
prompt: 'A cute cartoon bear with a red hat, watercolor style',
model: 'gemini-flash-image',
});
// Step 2: Use in different scenes
const scenes = ['playing in the park', 'reading a book', 'eating honey'];
for (const scene of scenes) {
const result = await service.generate({
prompt: scene,
model: 'gemini-flash-image',
referenceImages: [{ base64: bear.images[0].base64!, mimeType: 'image/png' }],
subjectDescription: 'cute cartoon bear with red hat', // Required in structured mode
});
// Save result...
}
`
$3
Best for scenes with multiple distinct characters. Reference images directly in your prompt by their position:
`typescript
// Load two different character references
const cowboy1 = await loadImage('cowboy1.png');
const cowboy2 = await loadImage('cowboy2.png');
// Reference each image by index in the prompt
const duelScene = await service.generate({
prompt: Generate a cinematic wide shot of a western duel.
,
model: 'gemini-flash-image',
referenceImages: [
{ base64: cowboy1, mimeType: 'image/png' },
{ base64: cowboy2, mimeType: 'image/png' },
],
// subjectDescription intentionally omitted for index-based mode
aspectRatio: '16:9',
});
`
Reference keywords: Use "FIRST reference image", "SECOND reference image" or "Image 1", "Image 2" etc.
$3
| Mode | subjectDescription | Use Case |
|------|---------------------|----------|
| Structured | Required | Single character across scenes |
| Index-Based | Omitted | Multiple characters in one scene |
- Model must be gemini-flash-image (only model supporting character consistency)
GDPR / Compliance
$3
| Provider | DPA | GDPR | EU Data Residency | Document |
|----------|-----|------|-------------------|----------|
| Google Cloud | Yes | Yes | Yes | CDPA |
| Eden AI | Yes | Depends | Depends | Privacy Policy |
| IONOS | Yes | Yes | Yes | AGB |
*Eden AI is an aggregator - compliance depends on the underlying provider.
$3
- Customer data is NOT used for training AI models
- Data stays in configured region (e.g., europe-west4)
- Zero data retention option available
- Vertex AI Privacy Whitepaper
Checking EU Region Status
`typescript
import { GoogleCloudTTIProvider } from '@loonylabs/tti-middleware';
const provider = new GoogleCloudTTIProvider({
projectId: 'my-project',
region: 'europe-west4',
});
console.log('Is EU region:', provider.isEURegion()); // true
console.log('Current region:', provider.getRegion()); // 'europe-west4'
`
API Reference
$3
`typescript
class TTIService {
registerProvider(provider: BaseTTIProvider): void;
generate(request: TTIRequest, provider?: TTIProvider): Promise;
getProvider(name: TTIProvider): BaseTTIProvider | undefined;
listAllModels(): Array<{ provider: TTIProvider; models: ModelInfo[] }>;
}
`
$3
`typescript
interface TTIRequest {
prompt: string;
model?: string; // 'imagen-3', 'gemini-flash-image', etc.
n?: number; // Number of images (default: 1)
aspectRatio?: string; // '1:1', '16:9', '4:3', etc.
// Character consistency
referenceImages?: TTIReferenceImage[];
subjectDescription?: string;
// Retry configuration
retry?: boolean | RetryOptions; // true (default), false, or custom config
providerOptions?: Record;
}
`
$3
`typescript
interface TTIResponse {
images: TTIImage[];
metadata: {
provider: string;
model: string;
region?: string;
duration: number;
};
usage: {
imagesGenerated: number;
modelId: string;
};
billing?: { // Only if provider returns costs
cost: number;
currency: string;
source: 'provider' | 'estimated';
};
}
`
Advanced Features
Retry Configuration
Automatic retry with exponential backoff and jitter for transient errors (429, 408, 5xx, network timeouts). Follows Google Cloud best practices.
`typescript
// Default: 3 retries, exponential backoff (1s → 2s → 4s), jitter enabled
const result = await service.generate({
prompt: 'A sunset over mountains',
model: 'imagen-3',
// retry: true (default)
});
// Custom retry configuration
const result = await service.generate({
prompt: 'A sunset over mountains',
model: 'imagen-3',
retry: {
maxRetries: 5,
delayMs: 1000,
backoffMultiplier: 2.0, // 1s, 2s, 4s, 8s, 16s
maxDelayMs: 30000, // Cap at 30s
jitter: true, // Randomize to prevent thundering herd
},
});
// Disable retry
const result = await service.generate({
prompt: 'A sunset over mountains',
model: 'imagen-3',
retry: false,
});
`
Retryable errors: 429, 408, 500, 502, 503, 504, timeouts, ECONNRESET, ECONNREFUSED, socket hang up
Not retried: 400, 401, 403, and other client errors
| Option | Default | Description |
|--------|---------|-------------|
| maxRetries | 3 | Maximum retry attempts |
| delayMs | 1000 | Base delay between retries (ms) |
| backoffMultiplier | 2.0 | Exponential multiplier per attempt |
| maxDelayMs | 30000 | Maximum delay cap (ms) |
| jitter | true | Randomize delay to prevent thundering herd |
Logging Configuration
Control logging via environment variable or API:
`typescript
import { setLogLevel } from '@loonylabs/tti-middleware';
// Set log level programmatically
setLogLevel('warn'); // Only show warnings and errors
// Or via environment variable
// TTI_LOG_LEVEL=error
`
Available levels: debug, info, warn, error, silent
Debug Logging (Markdown Files)
Log all TTI requests and responses to markdown files for debugging:
`typescript
import { TTIDebugger } from '@loonylabs/tti-middleware';
// Enable via environment variable
// DEBUG_TTI_REQUESTS=true
// Or programmatically
TTIDebugger.setEnabled(true);
TTIDebugger.setLogsDir('./logs/tti/requests');
// Configure all options at once
TTIDebugger.configure({
enabled: true,
logsDir: './logs/tti/requests',
consoleLog: true, // Also log to console
includeBase64: false, // Exclude base64 data (default)
});
`
Log file contents:
- Provider, model, and region
- Full prompt text
- Subject description (for character consistency)
- Reference image metadata
- Response data (duration, image count)
- Errors with full details
Use case: Debug why character consistency isn't working by inspecting exactly what prompt and subjectDescription are being sent to the API.
Error Handling
Typed error classes for precise error handling:
`typescript
import {
TTIError,
InvalidConfigError,
QuotaExceededError,
ProviderUnavailableError,
GenerationFailedError,
NetworkError,
CapabilityNotSupportedError,
} from '@loonylabs/tti-middleware';
try {
const result = await service.generate({ prompt: 'test' });
} catch (error) {
if (error instanceof QuotaExceededError) {
console.log('Rate limit hit, try again later');
} else if (error instanceof CapabilityNotSupportedError) {
console.log('Model does not support this feature');
} else if (error instanceof TTIError) {
console.log(TTI Error [${error.code}]: ${error.message});
}
}
`
Testing
`bash
Run all tests
npm test
Unit tests only (123 tests, >95% coverage)
npm run test:unit
Unit tests with watch mode
npm run test:unit:watch
Unit tests with coverage report
npm run test:unit:coverage
Integration tests (requires TTI_INTEGRATION_TESTS=true)
npm run test:integration
CI/CD mode (unit tests only, in band)
npm run test:ci
Manual test scripts
npm run test:manual:google-cloud
`
$3
Integration tests make real API calls. They are skipped by default.
`bash
Enable and run integration tests
TTI_INTEGRATION_TESTS=true npm run test:integration
`
Prerequisites:
- GOOGLE_CLOUD_PROJECT environment variable
- GOOGLE_APPLICATION_CREDENTIALS pointing to service account JSON
Documentation
- Getting Started - Detailed setup guide
- Google Cloud Provider - Imagen 3 & Gemini Flash Image
- GDPR/Compliance - Data processing agreements
- Testing Guide - Unit & integration tests
- CHANGELOG - Release notes
Contributing
We welcome contributions! Please ensure:
1. Tests: Add tests for new features
2. Linting: Run npm run lint before committing
3. Conventions: Follow the existing project structure
1. Fork the repository
2. Create your feature branch (git checkout -b feature/amazing-feature)
3. Commit your changes (git commit -m 'Add some amazing feature')
4. Push to the branch (git push origin feature/amazing-feature`)