@loonylabs/tti-middleware 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md
CHANGED
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
- **Eden AI**: Aggregator with access to OpenAI, Stability AI, Replicate (experimental)
|
|
46
46
|
- **IONOS**: German cloud provider with OpenAI-compatible API (experimental)
|
|
47
47
|
- **Character Consistency**: Generate consistent characters across multiple images (perfect for children's book illustrations)
|
|
48
|
-
- **Inpainting**: Fix specific areas of a generated image without regenerating the entire scene — via Vertex AI `imagen-capability` model.
|
|
48
|
+
- **Inpainting**: Fix specific areas of a generated image without regenerating the entire scene — via Vertex AI `imagen-capability` model.
|
|
49
49
|
- **GDPR/DSGVO Compliance**: Built-in EU region support with automatic fallback
|
|
50
50
|
- **Region Rotation**: Opt-in region rotation on quota errors (429) for Google Cloud — rotate through regions instead of retrying the same exhausted region
|
|
51
51
|
- **Retry Logic**: Exponential backoff with jitter for transient errors (429, 408, 5xx, timeouts)
|
|
@@ -331,41 +331,6 @@ const result = await service.generate({
|
|
|
331
331
|
- Black pixels = area preserved exactly as-is
|
|
332
332
|
- Mask must have identical dimensions to `baseImage`
|
|
333
333
|
|
|
334
|
-
### Guided Inpainting with Subject References
|
|
335
|
-
|
|
336
|
-
Use `maskReferenceImages` to provide a reference photo of the subject to insert — e.g. "place **this** character into the masked region":
|
|
337
|
-
|
|
338
|
-
```typescript
|
|
339
|
-
const result = await service.generate({
|
|
340
|
-
model: 'imagen-capability',
|
|
341
|
-
prompt: 'The character standing in a bright forest clearing, photorealistic',
|
|
342
|
-
baseImage: { base64: sceneBase64, mimeType: 'image/png' },
|
|
343
|
-
maskImage: { base64: maskBase64, mimeType: 'image/png' },
|
|
344
|
-
editMode: 'inpainting-insert',
|
|
345
|
-
maskReferenceImages: [
|
|
346
|
-
{
|
|
347
|
-
base64: characterRefBase64,
|
|
348
|
-
mimeType: 'image/png',
|
|
349
|
-
subjectType: 'person', // 'person' | 'animal' | 'product' | 'default'
|
|
350
|
-
},
|
|
351
|
-
],
|
|
352
|
-
});
|
|
353
|
-
```
|
|
354
|
-
|
|
355
|
-
**Subject types:**
|
|
356
|
-
|
|
357
|
-
| `subjectType` | Use case |
|
|
358
|
-
|---------------|----------|
|
|
359
|
-
| `'person'` | Human character |
|
|
360
|
-
| `'animal'` | Animal or creature |
|
|
361
|
-
| `'product'` | Object, item, product |
|
|
362
|
-
| `'default'` | Let the model decide (safe fallback) |
|
|
363
|
-
|
|
364
|
-
**Notes:**
|
|
365
|
-
- `maskReferenceImages` only works with `editMode: 'inpainting-insert'`
|
|
366
|
-
- Gemini models do **not** support mask-based inpainting or `maskReferenceImages`
|
|
367
|
-
- `maskReferenceImages` without `baseImage` throws a validation error
|
|
368
|
-
|
|
369
334
|
### Supported `editMode` Values
|
|
370
335
|
|
|
371
336
|
| Value | Description |
|
|
@@ -442,21 +407,12 @@ interface TTIRequest {
|
|
|
442
407
|
maskImage?: TTIReferenceImage; // Required when baseImage is set
|
|
443
408
|
maskDilation?: number; // 0.0–1.0, default 0.01
|
|
444
409
|
editMode?: 'inpainting-insert' | 'inpainting-remove' | 'background-swap' | 'outpainting';
|
|
445
|
-
maskReferenceImages?: TTIMaskReferenceImage[]; // Subject refs for guided inpainting
|
|
446
410
|
|
|
447
411
|
// Retry configuration
|
|
448
412
|
retry?: boolean | RetryOptions; // true (default), false, or custom config
|
|
449
413
|
|
|
450
414
|
providerOptions?: Record<string, unknown>;
|
|
451
415
|
}
|
|
452
|
-
|
|
453
|
-
type TTISubjectType = 'person' | 'animal' | 'product' | 'default';
|
|
454
|
-
|
|
455
|
-
interface TTIMaskReferenceImage {
|
|
456
|
-
base64: string;
|
|
457
|
-
mimeType?: string;
|
|
458
|
-
subjectType?: TTISubjectType; // defaults to 'default'
|
|
459
|
-
}
|
|
460
416
|
```
|
|
461
417
|
|
|
462
418
|
### TTIResponse
|
|
@@ -216,17 +216,6 @@ class BaseTTIProvider {
|
|
|
216
216
|
throw new InvalidConfigError(this.providerName, 'maskDilation must be between 0.0 and 1.0');
|
|
217
217
|
}
|
|
218
218
|
}
|
|
219
|
-
if (request.maskReferenceImages && request.maskReferenceImages.length > 0) {
|
|
220
|
-
for (let i = 0; i < request.maskReferenceImages.length; i++) {
|
|
221
|
-
const ref = request.maskReferenceImages[i];
|
|
222
|
-
if (!ref.base64 || ref.base64.trim().length === 0) {
|
|
223
|
-
throw new InvalidConfigError(this.providerName, `maskReferenceImages[${i}] has empty base64 data`);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
if (request.maskReferenceImages && request.maskReferenceImages.length > 0 && !request.baseImage) {
|
|
229
|
-
throw new InvalidConfigError(this.providerName, 'maskReferenceImages requires baseImage to be set');
|
|
230
219
|
}
|
|
231
220
|
// If reference images are provided, validate them
|
|
232
221
|
if (request.referenceImages && request.referenceImages.length > 0) {
|
|
@@ -565,18 +565,31 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
565
565
|
try {
|
|
566
566
|
const { client, helpers } = await this.getAiplatformClient(region);
|
|
567
567
|
const endpoint = `projects/${this.config.projectId}/locations/${region}/publishers/google/models/${internalModelId}`;
|
|
568
|
-
// Build referenceImages array
|
|
568
|
+
// Build referenceImages array.
|
|
569
|
+
//
|
|
570
|
+
// Vertex AI uses sequential, distinct referenceId values for each entry.
|
|
571
|
+
// RAW and MASK have separate IDs — the API pairs them by type/order, not by shared ID.
|
|
572
|
+
// SUBJECT gets the next ID after MASK and MUST be cited in the prompt as [N].
|
|
573
|
+
//
|
|
574
|
+
// Correct layout (confirmed by official Vertex AI docs):
|
|
575
|
+
// REFERENCE_TYPE_RAW referenceId: 1
|
|
576
|
+
// REFERENCE_TYPE_MASK referenceId: 2
|
|
577
|
+
// REFERENCE_TYPE_SUBJECT referenceId: 3 ← referenced in prompt as [3]
|
|
578
|
+
//
|
|
579
|
+
// See: https://cloud.google.com/vertex-ai/generative-ai/docs/image/edit-insert-objects
|
|
580
|
+
const RAW_REFERENCE_ID = 1;
|
|
581
|
+
const MASK_REFERENCE_ID = 2;
|
|
569
582
|
const referenceImages = [
|
|
570
583
|
{
|
|
571
584
|
referenceType: 'REFERENCE_TYPE_RAW',
|
|
572
|
-
referenceId:
|
|
585
|
+
referenceId: RAW_REFERENCE_ID,
|
|
573
586
|
referenceImage: {
|
|
574
587
|
bytesBase64Encoded: request.baseImage.base64,
|
|
575
588
|
},
|
|
576
589
|
},
|
|
577
590
|
{
|
|
578
591
|
referenceType: 'REFERENCE_TYPE_MASK',
|
|
579
|
-
referenceId:
|
|
592
|
+
referenceId: MASK_REFERENCE_ID,
|
|
580
593
|
referenceImage: {
|
|
581
594
|
bytesBase64Encoded: request.maskImage.base64,
|
|
582
595
|
},
|
|
@@ -586,23 +599,6 @@ class GoogleCloudTTIProvider extends base_tti_provider_1.BaseTTIProvider {
|
|
|
586
599
|
},
|
|
587
600
|
},
|
|
588
601
|
];
|
|
589
|
-
// Append optional subject reference images for guided inpainting
|
|
590
|
-
if (request.maskReferenceImages && request.maskReferenceImages.length > 0) {
|
|
591
|
-
for (const [i, ref] of request.maskReferenceImages.entries()) {
|
|
592
|
-
const subjectType = GoogleCloudTTIProvider.SUBJECT_TYPE_MAP[ref.subjectType ?? 'default'] ??
|
|
593
|
-
'SUBJECT_TYPE_DEFAULT';
|
|
594
|
-
referenceImages.push({
|
|
595
|
-
referenceType: 'REFERENCE_TYPE_SUBJECT',
|
|
596
|
-
referenceId: 3 + i,
|
|
597
|
-
referenceImage: {
|
|
598
|
-
bytesBase64Encoded: ref.base64,
|
|
599
|
-
},
|
|
600
|
-
subjectImageConfig: {
|
|
601
|
-
subjectType,
|
|
602
|
-
},
|
|
603
|
-
});
|
|
604
|
-
}
|
|
605
|
-
}
|
|
606
602
|
const instanceValue = {
|
|
607
603
|
prompt: request.prompt,
|
|
608
604
|
referenceImages,
|
|
@@ -832,7 +828,8 @@ GoogleCloudTTIProvider.EDIT_MODE_MAP = {
|
|
|
832
828
|
};
|
|
833
829
|
GoogleCloudTTIProvider.SUBJECT_TYPE_MAP = {
|
|
834
830
|
person: 'SUBJECT_TYPE_PERSON',
|
|
835
|
-
|
|
831
|
+
// Vertex AI API uses ANIMAL_COMPANION, not ANIMAL
|
|
832
|
+
animal: 'SUBJECT_TYPE_ANIMAL_COMPANION',
|
|
836
833
|
product: 'SUBJECT_TYPE_PRODUCT',
|
|
837
834
|
default: 'SUBJECT_TYPE_DEFAULT',
|
|
838
835
|
};
|
|
@@ -82,30 +82,6 @@ export interface TTIReferenceImage {
|
|
|
82
82
|
/** MIME type of the image (e.g., 'image/png', 'image/jpeg') */
|
|
83
83
|
mimeType?: string;
|
|
84
84
|
}
|
|
85
|
-
/**
|
|
86
|
-
* Subject type hint for mask reference images.
|
|
87
|
-
* Helps the model understand what kind of subject is shown in the reference image.
|
|
88
|
-
* - 'person' — a human character
|
|
89
|
-
* - 'animal' — an animal or creature
|
|
90
|
-
* - 'product' — an object, product, or item
|
|
91
|
-
* - 'default' — let the model decide (fallback)
|
|
92
|
-
*/
|
|
93
|
-
export type TTISubjectType = 'person' | 'animal' | 'product' | 'default';
|
|
94
|
-
/**
|
|
95
|
-
* Reference image for mask-based inpainting (subject reference).
|
|
96
|
-
* Used with maskReferenceImages to guide what the model inserts into the masked area.
|
|
97
|
-
*/
|
|
98
|
-
export interface TTIMaskReferenceImage {
|
|
99
|
-
/** Base64-encoded image data of the subject to insert */
|
|
100
|
-
base64: string;
|
|
101
|
-
/** MIME type of the image (e.g., 'image/png', 'image/jpeg') */
|
|
102
|
-
mimeType?: string;
|
|
103
|
-
/**
|
|
104
|
-
* Subject type hint for the model.
|
|
105
|
-
* Defaults to 'default' if omitted.
|
|
106
|
-
*/
|
|
107
|
-
subjectType?: TTISubjectType;
|
|
108
|
-
}
|
|
109
85
|
/**
|
|
110
86
|
* Unified TTI generation request
|
|
111
87
|
* Works for both simple text-to-image and character consistency
|
|
@@ -153,14 +129,6 @@ export interface TTIRequest {
|
|
|
153
129
|
* - 'outpainting': extend image beyond its boundaries into the masked area
|
|
154
130
|
*/
|
|
155
131
|
editMode?: 'inpainting-insert' | 'inpainting-remove' | 'background-swap' | 'outpainting';
|
|
156
|
-
/**
|
|
157
|
-
* Optional subject reference images for mask-based inpainting.
|
|
158
|
-
* Only valid when baseImage and maskImage are set.
|
|
159
|
-
* Each entry guides the model to insert a specific subject into the masked area
|
|
160
|
-
* (e.g., "place the character from this reference image into the mask").
|
|
161
|
-
* Only supported by the 'imagen-capability' model.
|
|
162
|
-
*/
|
|
163
|
-
maskReferenceImages?: TTIMaskReferenceImage[];
|
|
164
132
|
/** Additional provider-specific options */
|
|
165
133
|
providerOptions?: Record<string, unknown>;
|
|
166
134
|
/**
|
package/package.json
CHANGED
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@loonylabs/tti-middleware",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Provider-agnostic Text-to-Image middleware with GDPR compliance. Supports Google Cloud (Imagen, Gemini), Eden AI, and IONOS.",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"files": [
|
|
8
|
-
"dist",
|
|
9
|
-
"LICENSE",
|
|
10
|
-
"README.md",
|
|
11
|
-
".env.example"
|
|
12
|
-
],
|
|
13
|
-
"scripts": {
|
|
14
|
-
"build": "tsc",
|
|
15
|
-
"test": "npm run test:unit",
|
|
16
|
-
"test:unit": "jest --testPathPattern=tests/unit",
|
|
17
|
-
"test:unit:watch": "jest --testPathPattern=tests/unit --watch",
|
|
18
|
-
"test:unit:coverage": "jest --testPathPattern=tests/unit --coverage",
|
|
19
|
-
"test:integration": "cross-env TTI_INTEGRATION_TESTS=true jest --testPathPattern=tests/integration",
|
|
20
|
-
"test:ci": "jest --runInBand --ci --coverage --testPathPattern=tests/unit",
|
|
21
|
-
"test:live": "TTI_INTEGRATION_TESTS=true jest tests/integration/*.integration.test.ts --testTimeout=120000",
|
|
22
|
-
"test:manual": "ts-node scripts/manual-test-edenai.ts",
|
|
23
|
-
"test:manual:google-cloud": "ts-node scripts/manual-test-google-cloud.ts",
|
|
24
|
-
"lint": "eslint src/**/*.ts",
|
|
25
|
-
"format": "prettier --write \"src/**/*.ts\"",
|
|
26
|
-
"clean": "node -e \"const fs=require('fs');if(fs.existsSync('dist'))fs.rmSync('dist',{recursive:true})\"",
|
|
27
|
-
"prepare": "npm run build",
|
|
28
|
-
"prepublishOnly": "npm run clean && npm run build && npm run test"
|
|
29
|
-
},
|
|
30
|
-
"keywords": [
|
|
31
|
-
"tti",
|
|
32
|
-
"text-to-image",
|
|
33
|
-
"image-generation",
|
|
34
|
-
"ai",
|
|
35
|
-
"vertex-ai",
|
|
36
|
-
"google-cloud",
|
|
37
|
-
"gemini",
|
|
38
|
-
"imagen",
|
|
39
|
-
"middleware",
|
|
40
|
-
"provider-agnostic",
|
|
41
|
-
"typescript",
|
|
42
|
-
"gdpr",
|
|
43
|
-
"dsgvo",
|
|
44
|
-
"character-consistency"
|
|
45
|
-
],
|
|
46
|
-
"author": "loonylabs-dev",
|
|
47
|
-
"license": "MIT",
|
|
48
|
-
"repository": {
|
|
49
|
-
"type": "git",
|
|
50
|
-
"url": "https://github.com/loonylabs-dev/tti-middleware.git"
|
|
51
|
-
},
|
|
52
|
-
"bugs": {
|
|
53
|
-
"url": "https://github.com/loonylabs-dev/tti-middleware/issues"
|
|
54
|
-
},
|
|
55
|
-
"homepage": "https://github.com/loonylabs-dev/tti-middleware#readme",
|
|
56
|
-
"peerDependencies": {
|
|
57
|
-
"@google-cloud/aiplatform": ">=3.0.0",
|
|
58
|
-
"@google/genai": ">=1.40.0"
|
|
59
|
-
},
|
|
60
|
-
"peerDependenciesMeta": {
|
|
61
|
-
"@google-cloud/aiplatform": {
|
|
62
|
-
"optional": true
|
|
63
|
-
},
|
|
64
|
-
"@google/genai": {
|
|
65
|
-
"optional": true
|
|
66
|
-
}
|
|
67
|
-
},
|
|
68
|
-
"devDependencies": {
|
|
69
|
-
"@google-cloud/aiplatform": "^3.29.0",
|
|
70
|
-
"@google/genai": "^1.40.0",
|
|
71
|
-
"@types/jest": "^29.5.8",
|
|
72
|
-
"@types/node": "^20.10.0",
|
|
73
|
-
"@typescript-eslint/eslint-plugin": "^6.13.0",
|
|
74
|
-
"@typescript-eslint/parser": "^6.13.0",
|
|
75
|
-
"cross-env": "^10.1.0",
|
|
76
|
-
"dotenv": "^17.2.3",
|
|
77
|
-
"eslint": "^8.54.0",
|
|
78
|
-
"jest": "^29.7.0",
|
|
79
|
-
"prettier": "^3.1.0",
|
|
80
|
-
"ts-jest": "^29.1.1",
|
|
81
|
-
"ts-node": "^10.9.2",
|
|
82
|
-
"typescript": "^5.3.2"
|
|
83
|
-
},
|
|
84
|
-
"engines": {
|
|
85
|
-
"node": ">=18.0.0"
|
|
86
|
-
},
|
|
87
|
-
"publishConfig": {
|
|
88
|
-
"access": "public"
|
|
89
|
-
}
|
|
90
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@loonylabs/tti-middleware",
|
|
3
|
+
"version": "1.12.0",
|
|
4
|
+
"description": "Provider-agnostic Text-to-Image middleware with GDPR compliance. Supports Google Cloud (Imagen, Gemini), Eden AI, and IONOS.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"LICENSE",
|
|
10
|
+
"README.md",
|
|
11
|
+
".env.example"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"test": "npm run test:unit",
|
|
16
|
+
"test:unit": "jest --testPathPattern=tests/unit",
|
|
17
|
+
"test:unit:watch": "jest --testPathPattern=tests/unit --watch",
|
|
18
|
+
"test:unit:coverage": "jest --testPathPattern=tests/unit --coverage",
|
|
19
|
+
"test:integration": "cross-env TTI_INTEGRATION_TESTS=true jest --testPathPattern=tests/integration",
|
|
20
|
+
"test:ci": "jest --runInBand --ci --coverage --testPathPattern=tests/unit",
|
|
21
|
+
"test:live": "TTI_INTEGRATION_TESTS=true jest tests/integration/*.integration.test.ts --testTimeout=120000",
|
|
22
|
+
"test:manual": "ts-node scripts/manual-test-edenai.ts",
|
|
23
|
+
"test:manual:google-cloud": "ts-node scripts/manual-test-google-cloud.ts",
|
|
24
|
+
"lint": "eslint src/**/*.ts",
|
|
25
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
26
|
+
"clean": "node -e \"const fs=require('fs');if(fs.existsSync('dist'))fs.rmSync('dist',{recursive:true})\"",
|
|
27
|
+
"prepare": "npm run build",
|
|
28
|
+
"prepublishOnly": "npm run clean && npm run build && npm run test"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"tti",
|
|
32
|
+
"text-to-image",
|
|
33
|
+
"image-generation",
|
|
34
|
+
"ai",
|
|
35
|
+
"vertex-ai",
|
|
36
|
+
"google-cloud",
|
|
37
|
+
"gemini",
|
|
38
|
+
"imagen",
|
|
39
|
+
"middleware",
|
|
40
|
+
"provider-agnostic",
|
|
41
|
+
"typescript",
|
|
42
|
+
"gdpr",
|
|
43
|
+
"dsgvo",
|
|
44
|
+
"character-consistency"
|
|
45
|
+
],
|
|
46
|
+
"author": "loonylabs-dev",
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "https://github.com/loonylabs-dev/tti-middleware.git"
|
|
51
|
+
},
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/loonylabs-dev/tti-middleware/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/loonylabs-dev/tti-middleware#readme",
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@google-cloud/aiplatform": ">=3.0.0",
|
|
58
|
+
"@google/genai": ">=1.40.0"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"@google-cloud/aiplatform": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@google/genai": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@google-cloud/aiplatform": "^3.29.0",
|
|
70
|
+
"@google/genai": "^1.40.0",
|
|
71
|
+
"@types/jest": "^29.5.8",
|
|
72
|
+
"@types/node": "^20.10.0",
|
|
73
|
+
"@typescript-eslint/eslint-plugin": "^6.13.0",
|
|
74
|
+
"@typescript-eslint/parser": "^6.13.0",
|
|
75
|
+
"cross-env": "^10.1.0",
|
|
76
|
+
"dotenv": "^17.2.3",
|
|
77
|
+
"eslint": "^8.54.0",
|
|
78
|
+
"jest": "^29.7.0",
|
|
79
|
+
"prettier": "^3.1.0",
|
|
80
|
+
"ts-jest": "^29.1.1",
|
|
81
|
+
"ts-node": "^10.9.2",
|
|
82
|
+
"typescript": "^5.3.2"
|
|
83
|
+
},
|
|
84
|
+
"engines": {
|
|
85
|
+
"node": ">=18.0.0"
|
|
86
|
+
},
|
|
87
|
+
"publishConfig": {
|
|
88
|
+
"access": "public"
|
|
89
|
+
}
|
|
90
|
+
}
|