@contractspec/lib.image-gen 0.2.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.
Files changed (86) hide show
  1. package/dist/browser/docs/generators.docblock.js +36 -0
  2. package/dist/browser/docs/image-gen.docblock.js +47 -0
  3. package/dist/browser/generators/image-generator.js +525 -0
  4. package/dist/browser/generators/index.js +527 -0
  5. package/dist/browser/generators/prompt-builder.js +93 -0
  6. package/dist/browser/generators/style-resolver.js +90 -0
  7. package/dist/browser/i18n/catalogs/en.js +85 -0
  8. package/dist/browser/i18n/catalogs/es.js +85 -0
  9. package/dist/browser/i18n/catalogs/fr.js +85 -0
  10. package/dist/browser/i18n/catalogs/index.js +253 -0
  11. package/dist/browser/i18n/index.js +309 -0
  12. package/dist/browser/i18n/keys.js +33 -0
  13. package/dist/browser/i18n/locale.js +13 -0
  14. package/dist/browser/i18n/messages.js +265 -0
  15. package/dist/browser/index.js +623 -0
  16. package/dist/browser/presets/index.js +99 -0
  17. package/dist/browser/presets/marketing.js +35 -0
  18. package/dist/browser/presets/social.js +45 -0
  19. package/dist/browser/presets/video.js +25 -0
  20. package/dist/browser/types.js +5 -0
  21. package/dist/docs/generators.docblock.d.ts +1 -0
  22. package/dist/docs/generators.docblock.js +37 -0
  23. package/dist/docs/image-gen.docblock.d.ts +1 -0
  24. package/dist/docs/image-gen.docblock.js +48 -0
  25. package/dist/generators/image-generator.d.ts +12 -0
  26. package/dist/generators/image-generator.js +526 -0
  27. package/dist/generators/image-generator.test.d.ts +1 -0
  28. package/dist/generators/index.d.ts +4 -0
  29. package/dist/generators/index.js +528 -0
  30. package/dist/generators/prompt-builder.d.ts +21 -0
  31. package/dist/generators/prompt-builder.js +94 -0
  32. package/dist/generators/prompt-builder.test.d.ts +1 -0
  33. package/dist/generators/style-resolver.d.ts +9 -0
  34. package/dist/generators/style-resolver.js +91 -0
  35. package/dist/generators/style-resolver.test.d.ts +1 -0
  36. package/dist/i18n/catalogs/en.d.ts +8 -0
  37. package/dist/i18n/catalogs/en.js +86 -0
  38. package/dist/i18n/catalogs/es.d.ts +6 -0
  39. package/dist/i18n/catalogs/es.js +86 -0
  40. package/dist/i18n/catalogs/fr.d.ts +6 -0
  41. package/dist/i18n/catalogs/fr.js +86 -0
  42. package/dist/i18n/catalogs/index.d.ts +3 -0
  43. package/dist/i18n/catalogs/index.js +254 -0
  44. package/dist/i18n/i18n.test.d.ts +1 -0
  45. package/dist/i18n/index.d.ts +6 -0
  46. package/dist/i18n/index.js +310 -0
  47. package/dist/i18n/keys.d.ts +78 -0
  48. package/dist/i18n/keys.js +34 -0
  49. package/dist/i18n/locale.d.ts +8 -0
  50. package/dist/i18n/locale.js +14 -0
  51. package/dist/i18n/messages.d.ts +14 -0
  52. package/dist/i18n/messages.js +266 -0
  53. package/dist/index.d.ts +3 -0
  54. package/dist/index.js +624 -0
  55. package/dist/node/docs/generators.docblock.js +36 -0
  56. package/dist/node/docs/image-gen.docblock.js +47 -0
  57. package/dist/node/generators/image-generator.js +525 -0
  58. package/dist/node/generators/index.js +527 -0
  59. package/dist/node/generators/prompt-builder.js +93 -0
  60. package/dist/node/generators/style-resolver.js +90 -0
  61. package/dist/node/i18n/catalogs/en.js +85 -0
  62. package/dist/node/i18n/catalogs/es.js +85 -0
  63. package/dist/node/i18n/catalogs/fr.js +85 -0
  64. package/dist/node/i18n/catalogs/index.js +253 -0
  65. package/dist/node/i18n/index.js +309 -0
  66. package/dist/node/i18n/keys.js +33 -0
  67. package/dist/node/i18n/locale.js +13 -0
  68. package/dist/node/i18n/messages.js +265 -0
  69. package/dist/node/index.js +623 -0
  70. package/dist/node/presets/index.js +99 -0
  71. package/dist/node/presets/marketing.js +35 -0
  72. package/dist/node/presets/social.js +45 -0
  73. package/dist/node/presets/video.js +25 -0
  74. package/dist/node/types.js +5 -0
  75. package/dist/presets/index.d.ts +3 -0
  76. package/dist/presets/index.js +100 -0
  77. package/dist/presets/marketing.d.ts +8 -0
  78. package/dist/presets/marketing.js +36 -0
  79. package/dist/presets/presets.test.d.ts +1 -0
  80. package/dist/presets/social.d.ts +10 -0
  81. package/dist/presets/social.js +46 -0
  82. package/dist/presets/video.d.ts +6 -0
  83. package/dist/presets/video.js +26 -0
  84. package/dist/types.d.ts +56 -0
  85. package/dist/types.js +6 -0
  86. package/package.json +394 -0
package/dist/index.js ADDED
@@ -0,0 +1,624 @@
1
+ // @bun
2
+ // src/i18n/catalogs/en.ts
3
+ import { defineTranslation } from "@contractspec/lib.contracts-spec/translations";
4
+ var enMessages = defineTranslation({
5
+ meta: {
6
+ key: "image-gen.messages",
7
+ version: "1.0.0",
8
+ domain: "image-gen",
9
+ description: "All user-facing, LLM-facing, and developer-facing strings for the image-gen package",
10
+ owners: ["platform"],
11
+ stability: "experimental"
12
+ },
13
+ locale: "en",
14
+ fallback: "en",
15
+ messages: {
16
+ "prompt.system.imagePromptEngineer": {
17
+ value: "You are an expert image prompt engineer. Given a JSON brief containing title, summary, problems, solutions, purpose, style, and style tokens, produce a single detailed image generation prompt. The prompt should be vivid, specific, and optimized for AI image generation models. Focus on composition, lighting, color palette, and subject matter. Output only the prompt text, no JSON.",
18
+ description: "System prompt for LLM-based image prompt engineering"
19
+ },
20
+ "image.generate.description": {
21
+ value: "Generate a {style} image for {purpose}",
22
+ description: "Description template for image generation tasks",
23
+ placeholders: [
24
+ { name: "style", type: "string" },
25
+ { name: "purpose", type: "string" }
26
+ ]
27
+ },
28
+ "image.prompt.featuring": {
29
+ value: "featuring {solutions}",
30
+ description: "Prompt fragment for featured solutions",
31
+ placeholders: [{ name: "solutions", type: "string" }]
32
+ },
33
+ "image.prompt.industryContext": {
34
+ value: "{industry} context",
35
+ description: "Prompt fragment for industry context",
36
+ placeholders: [{ name: "industry", type: "string" }]
37
+ },
38
+ "image.error.noProvider": {
39
+ value: "No image provider configured",
40
+ description: "Error message when no ImageProvider is available"
41
+ },
42
+ "image.error.generationFailed": {
43
+ value: "Image generation failed",
44
+ description: "Error message when image generation fails"
45
+ },
46
+ "purpose.blogHero": {
47
+ value: "Blog hero image",
48
+ description: "Label for blog hero image purpose"
49
+ },
50
+ "purpose.socialOg": {
51
+ value: "Social media OG image",
52
+ description: "Label for Open Graph image purpose"
53
+ },
54
+ "purpose.socialTwitter": {
55
+ value: "Twitter card image",
56
+ description: "Label for Twitter card image purpose"
57
+ },
58
+ "purpose.socialInstagram": {
59
+ value: "Instagram image",
60
+ description: "Label for Instagram image purpose"
61
+ },
62
+ "purpose.landingHero": {
63
+ value: "Landing page hero",
64
+ description: "Label for landing page hero image purpose"
65
+ },
66
+ "purpose.videoThumbnail": {
67
+ value: "Video thumbnail",
68
+ description: "Label for video thumbnail purpose"
69
+ },
70
+ "purpose.emailHeader": {
71
+ value: "Email header",
72
+ description: "Label for email header image purpose"
73
+ },
74
+ "purpose.illustration": {
75
+ value: "Illustration",
76
+ description: "Label for illustration purpose"
77
+ },
78
+ "purpose.icon": {
79
+ value: "Icon",
80
+ description: "Label for icon purpose"
81
+ }
82
+ }
83
+ });
84
+
85
+ // src/i18n/catalogs/fr.ts
86
+ import { defineTranslation as defineTranslation2 } from "@contractspec/lib.contracts-spec/translations";
87
+ var frMessages = defineTranslation2({
88
+ meta: {
89
+ key: "image-gen.messages",
90
+ version: "1.0.0",
91
+ domain: "image-gen",
92
+ description: "French translations for the image-gen package",
93
+ owners: ["platform"],
94
+ stability: "experimental"
95
+ },
96
+ locale: "fr",
97
+ fallback: "en",
98
+ messages: {
99
+ "prompt.system.imagePromptEngineer": {
100
+ value: "Vous \xEAtes un expert en ing\xE9nierie de prompts d'images. \xC0 partir d'un brief JSON contenant titre, r\xE9sum\xE9, probl\xE8mes, solutions, objectif, style et tokens de style, produisez un prompt d\xE9taill\xE9 de g\xE9n\xE9ration d'image. Le prompt doit \xEAtre vivant, sp\xE9cifique et optimis\xE9 pour les mod\xE8les de g\xE9n\xE9ration d'images IA. Concentrez-vous sur la composition, l'\xE9clairage, la palette de couleurs et le sujet. Produisez uniquement le texte du prompt, pas de JSON.",
101
+ description: "Prompt syst\xE8me pour l'ing\xE9nierie de prompts d'images par LLM"
102
+ },
103
+ "image.generate.description": {
104
+ value: "G\xE9n\xE9rer une image {style} pour {purpose}",
105
+ description: "Mod\xE8le de description pour les t\xE2ches de g\xE9n\xE9ration d'images",
106
+ placeholders: [
107
+ { name: "style", type: "string" },
108
+ { name: "purpose", type: "string" }
109
+ ]
110
+ },
111
+ "image.prompt.featuring": {
112
+ value: "mettant en avant {solutions}",
113
+ description: "Fragment de prompt pour les solutions mises en avant",
114
+ placeholders: [{ name: "solutions", type: "string" }]
115
+ },
116
+ "image.prompt.industryContext": {
117
+ value: "contexte {industry}",
118
+ description: "Fragment de prompt pour le contexte industriel",
119
+ placeholders: [{ name: "industry", type: "string" }]
120
+ },
121
+ "image.error.noProvider": {
122
+ value: "Aucun fournisseur d'images configur\xE9",
123
+ description: "Message d'erreur quand aucun ImageProvider n'est disponible"
124
+ },
125
+ "image.error.generationFailed": {
126
+ value: "La g\xE9n\xE9ration d'image a \xE9chou\xE9",
127
+ description: "Message d'erreur quand la g\xE9n\xE9ration d'image \xE9choue"
128
+ },
129
+ "purpose.blogHero": {
130
+ value: "Image hero de blog",
131
+ description: "Libell\xE9 pour l'objectif image hero de blog"
132
+ },
133
+ "purpose.socialOg": {
134
+ value: "Image OG pour r\xE9seaux sociaux",
135
+ description: "Libell\xE9 pour l'objectif image Open Graph"
136
+ },
137
+ "purpose.socialTwitter": {
138
+ value: "Image carte Twitter",
139
+ description: "Libell\xE9 pour l'objectif image carte Twitter"
140
+ },
141
+ "purpose.socialInstagram": {
142
+ value: "Image Instagram",
143
+ description: "Libell\xE9 pour l'objectif image Instagram"
144
+ },
145
+ "purpose.landingHero": {
146
+ value: "Hero de page d'atterrissage",
147
+ description: "Libell\xE9 pour l'objectif image hero de page d'atterrissage"
148
+ },
149
+ "purpose.videoThumbnail": {
150
+ value: "Miniature vid\xE9o",
151
+ description: "Libell\xE9 pour l'objectif miniature vid\xE9o"
152
+ },
153
+ "purpose.emailHeader": {
154
+ value: "En-t\xEAte d'email",
155
+ description: "Libell\xE9 pour l'objectif image en-t\xEAte d'email"
156
+ },
157
+ "purpose.illustration": {
158
+ value: "Illustration",
159
+ description: "Libell\xE9 pour l'objectif illustration"
160
+ },
161
+ "purpose.icon": {
162
+ value: "Ic\xF4ne",
163
+ description: "Libell\xE9 pour l'objectif ic\xF4ne"
164
+ }
165
+ }
166
+ });
167
+
168
+ // src/i18n/catalogs/es.ts
169
+ import { defineTranslation as defineTranslation3 } from "@contractspec/lib.contracts-spec/translations";
170
+ var esMessages = defineTranslation3({
171
+ meta: {
172
+ key: "image-gen.messages",
173
+ version: "1.0.0",
174
+ domain: "image-gen",
175
+ description: "Spanish translations for the image-gen package",
176
+ owners: ["platform"],
177
+ stability: "experimental"
178
+ },
179
+ locale: "es",
180
+ fallback: "en",
181
+ messages: {
182
+ "prompt.system.imagePromptEngineer": {
183
+ value: "Eres un experto en ingenier\xEDa de prompts de im\xE1genes. Dado un brief JSON que contiene t\xEDtulo, resumen, problemas, soluciones, prop\xF3sito, estilo y tokens de estilo, produce un prompt detallado de generaci\xF3n de im\xE1genes. El prompt debe ser v\xEDvido, espec\xEDfico y optimizado para modelos de generaci\xF3n de im\xE1genes con IA. Conc\xE9ntrate en la composici\xF3n, iluminaci\xF3n, paleta de colores y tema. Produce solo el texto del prompt, sin JSON.",
184
+ description: "Prompt del sistema para ingenier\xEDa de prompts de im\xE1genes por LLM"
185
+ },
186
+ "image.generate.description": {
187
+ value: "Generar una imagen {style} para {purpose}",
188
+ description: "Plantilla de descripci\xF3n para tareas de generaci\xF3n de im\xE1genes",
189
+ placeholders: [
190
+ { name: "style", type: "string" },
191
+ { name: "purpose", type: "string" }
192
+ ]
193
+ },
194
+ "image.prompt.featuring": {
195
+ value: "presentando {solutions}",
196
+ description: "Fragmento de prompt para soluciones destacadas",
197
+ placeholders: [{ name: "solutions", type: "string" }]
198
+ },
199
+ "image.prompt.industryContext": {
200
+ value: "contexto de {industry}",
201
+ description: "Fragmento de prompt para contexto de industria",
202
+ placeholders: [{ name: "industry", type: "string" }]
203
+ },
204
+ "image.error.noProvider": {
205
+ value: "No hay proveedor de im\xE1genes configurado",
206
+ description: "Mensaje de error cuando no hay ImageProvider disponible"
207
+ },
208
+ "image.error.generationFailed": {
209
+ value: "La generaci\xF3n de imagen fall\xF3",
210
+ description: "Mensaje de error cuando la generaci\xF3n de imagen falla"
211
+ },
212
+ "purpose.blogHero": {
213
+ value: "Imagen hero de blog",
214
+ description: "Etiqueta para el prop\xF3sito imagen hero de blog"
215
+ },
216
+ "purpose.socialOg": {
217
+ value: "Imagen OG para redes sociales",
218
+ description: "Etiqueta para el prop\xF3sito imagen Open Graph"
219
+ },
220
+ "purpose.socialTwitter": {
221
+ value: "Imagen de tarjeta Twitter",
222
+ description: "Etiqueta para el prop\xF3sito imagen tarjeta Twitter"
223
+ },
224
+ "purpose.socialInstagram": {
225
+ value: "Imagen de Instagram",
226
+ description: "Etiqueta para el prop\xF3sito imagen Instagram"
227
+ },
228
+ "purpose.landingHero": {
229
+ value: "Hero de p\xE1gina de aterrizaje",
230
+ description: "Etiqueta para el prop\xF3sito imagen hero de p\xE1gina de aterrizaje"
231
+ },
232
+ "purpose.videoThumbnail": {
233
+ value: "Miniatura de video",
234
+ description: "Etiqueta para el prop\xF3sito miniatura de video"
235
+ },
236
+ "purpose.emailHeader": {
237
+ value: "Encabezado de email",
238
+ description: "Etiqueta para el prop\xF3sito imagen encabezado de email"
239
+ },
240
+ "purpose.illustration": {
241
+ value: "Ilustraci\xF3n",
242
+ description: "Etiqueta para el prop\xF3sito ilustraci\xF3n"
243
+ },
244
+ "purpose.icon": {
245
+ value: "Icono",
246
+ description: "Etiqueta para el prop\xF3sito icono"
247
+ }
248
+ }
249
+ });
250
+
251
+ // src/i18n/messages.ts
252
+ import {
253
+ createI18nFactory
254
+ } from "@contractspec/lib.contracts-spec/translations";
255
+ var factory = createI18nFactory({
256
+ specKey: "image-gen.messages",
257
+ catalogs: [enMessages, frMessages, esMessages]
258
+ });
259
+ var createImageGenI18n = factory.create;
260
+ var getDefaultI18n = factory.getDefault;
261
+ var resetI18nRegistry = factory.resetRegistry;
262
+
263
+ // src/generators/prompt-builder.ts
264
+ class PromptBuilder {
265
+ llm;
266
+ model;
267
+ temperature;
268
+ i18n;
269
+ constructor(options) {
270
+ this.llm = options.llm;
271
+ this.model = options.model;
272
+ this.temperature = options.temperature ?? 0.4;
273
+ this.i18n = options.i18n;
274
+ }
275
+ async build(brief, resolvedStyle) {
276
+ const style = brief.style ?? "photorealistic";
277
+ const format = brief.format ?? "png";
278
+ if (this.llm) {
279
+ try {
280
+ return await this.buildWithLlm(brief, resolvedStyle, style, format);
281
+ } catch {
282
+ return this.buildDeterministic(brief, resolvedStyle, style, format);
283
+ }
284
+ }
285
+ return this.buildDeterministic(brief, resolvedStyle, style, format);
286
+ }
287
+ async buildWithLlm(brief, resolvedStyle, style, format) {
288
+ const systemPrompt = this.i18n.t("prompt.system.imagePromptEngineer");
289
+ const briefJson = JSON.stringify({
290
+ title: brief.content.title,
291
+ summary: brief.content.summary,
292
+ problems: brief.content.problems,
293
+ solutions: brief.content.solutions,
294
+ purpose: brief.purpose,
295
+ style,
296
+ styleTokens: resolvedStyle.styleTokens
297
+ });
298
+ const messages = [
299
+ {
300
+ role: "system",
301
+ content: [{ type: "text", text: systemPrompt }]
302
+ },
303
+ {
304
+ role: "user",
305
+ content: [{ type: "text", text: briefJson }]
306
+ }
307
+ ];
308
+ const llm = this.llm;
309
+ if (!llm) {
310
+ throw new Error("LLM provider is required for buildWithLlm");
311
+ }
312
+ const response = await llm.chat(messages, {
313
+ model: this.model,
314
+ temperature: this.temperature
315
+ });
316
+ const text = response.message.content.filter((block) => block.type === "text").map((block) => ("text" in block) ? block.text : "").join("");
317
+ return {
318
+ text: text.trim(),
319
+ negativeText: resolvedStyle.negativeTokens.join(", "),
320
+ style,
321
+ dimensions: resolvedStyle.dimensions,
322
+ format
323
+ };
324
+ }
325
+ buildDeterministic(brief, resolvedStyle, style, format) {
326
+ const parts = [
327
+ brief.content.title,
328
+ this.i18n.t("image.generate.description", {
329
+ style,
330
+ purpose: brief.purpose
331
+ })
332
+ ];
333
+ if (brief.content.solutions.length > 0) {
334
+ parts.push(this.i18n.t("image.prompt.featuring", {
335
+ solutions: brief.content.solutions.slice(0, 3).join(", ")
336
+ }));
337
+ }
338
+ if (brief.content.audience?.industry) {
339
+ parts.push(this.i18n.t("image.prompt.industryContext", {
340
+ industry: brief.content.audience.industry
341
+ }));
342
+ }
343
+ parts.push(...resolvedStyle.styleTokens);
344
+ return {
345
+ text: parts.join(", "),
346
+ negativeText: resolvedStyle.negativeTokens.join(", "),
347
+ style,
348
+ dimensions: resolvedStyle.dimensions,
349
+ format
350
+ };
351
+ }
352
+ }
353
+
354
+ // src/types.ts
355
+ import { IMAGE_PRESETS } from "@contractspec/lib.contracts-integrations/integrations/providers/image";
356
+ // src/generators/style-resolver.ts
357
+ var PURPOSE_DIMENSIONS = {
358
+ "blog-hero": IMAGE_PRESETS.blogHero,
359
+ "social-og": IMAGE_PRESETS.ogImage,
360
+ "social-twitter": IMAGE_PRESETS.twitterCard,
361
+ "social-instagram": IMAGE_PRESETS.instagramSquare,
362
+ "landing-hero": IMAGE_PRESETS.blogHero,
363
+ "video-thumbnail": IMAGE_PRESETS.thumbnail,
364
+ "email-header": IMAGE_PRESETS.emailHeader,
365
+ illustration: IMAGE_PRESETS.illustration,
366
+ icon: IMAGE_PRESETS.favicon
367
+ };
368
+ var STYLE_TOKENS = {
369
+ photorealistic: [
370
+ "professional photography",
371
+ "high resolution",
372
+ "natural lighting",
373
+ "detailed textures"
374
+ ],
375
+ illustration: [
376
+ "digital illustration",
377
+ "clean lines",
378
+ "vibrant colors",
379
+ "artistic composition"
380
+ ],
381
+ "3d-render": [
382
+ "3D render",
383
+ "volumetric lighting",
384
+ "glossy materials",
385
+ "depth of field"
386
+ ],
387
+ "flat-design": [
388
+ "clean flat vector",
389
+ "geometric shapes",
390
+ "solid colors",
391
+ "minimal shadows"
392
+ ],
393
+ abstract: [
394
+ "abstract composition",
395
+ "bold shapes",
396
+ "dynamic colors",
397
+ "artistic interpretation"
398
+ ],
399
+ minimalist: [
400
+ "minimalist design",
401
+ "clean whitespace",
402
+ "simple elements",
403
+ "restrained palette"
404
+ ],
405
+ branded: [
406
+ "brand-consistent design",
407
+ "professional layout",
408
+ "corporate aesthetic",
409
+ "clean composition"
410
+ ]
411
+ };
412
+ var BASE_NEGATIVE_TOKENS = [
413
+ "blurry",
414
+ "low quality",
415
+ "text overlay",
416
+ "watermark",
417
+ "distorted",
418
+ "pixelated"
419
+ ];
420
+
421
+ class StyleResolver {
422
+ resolve(purpose, style, brand) {
423
+ const resolvedStyle = style ?? "photorealistic";
424
+ const dimensions = PURPOSE_DIMENSIONS[purpose];
425
+ const styleTokens = [...STYLE_TOKENS[resolvedStyle]];
426
+ const negativeTokens = [...BASE_NEGATIVE_TOKENS];
427
+ if (brand) {
428
+ if (brand.primary) {
429
+ styleTokens.push(`primary color ${brand.primary} palette`);
430
+ }
431
+ if (brand.accent) {
432
+ styleTokens.push(`accent color ${brand.accent}`);
433
+ }
434
+ if (brand.background) {
435
+ styleTokens.push(`background color ${brand.background}`);
436
+ }
437
+ }
438
+ return { styleTokens, negativeTokens, dimensions };
439
+ }
440
+ }
441
+
442
+ // src/generators/image-generator.ts
443
+ var DEFAULT_CLOCK = {
444
+ now: () => Date.now(),
445
+ toISOString: () => new Date().toISOString()
446
+ };
447
+
448
+ class ImageGenerator {
449
+ promptBuilder;
450
+ styleResolver;
451
+ options;
452
+ i18n;
453
+ clock;
454
+ constructor(options) {
455
+ this.options = options ?? {};
456
+ this.i18n = createImageGenI18n(options?.locale);
457
+ this.clock = options?.clock ?? DEFAULT_CLOCK;
458
+ this.styleResolver = new StyleResolver;
459
+ this.promptBuilder = new PromptBuilder({
460
+ llm: options?.llm,
461
+ model: options?.model,
462
+ temperature: options?.temperature,
463
+ i18n: this.i18n
464
+ });
465
+ }
466
+ async generate(brief) {
467
+ const resolvedStyle = this.styleResolver.resolve(brief.purpose, brief.style ?? this.options.defaultStyle, brief.brandColors);
468
+ const prompt = await this.promptBuilder.build(brief, resolvedStyle);
469
+ if (brief.dimensions) {
470
+ prompt.dimensions = brief.dimensions;
471
+ }
472
+ const id = this.generateId(brief);
473
+ const project = {
474
+ id,
475
+ prompt,
476
+ metadata: {
477
+ purpose: brief.purpose,
478
+ title: brief.content.title,
479
+ createdAt: this.clock.toISOString(),
480
+ locale: brief.locale ?? this.options.locale ?? "en"
481
+ }
482
+ };
483
+ if (this.options.image) {
484
+ try {
485
+ const results = await this.options.image.generate({
486
+ prompt: prompt.text,
487
+ negativePrompt: prompt.negativeText,
488
+ dimensions: prompt.dimensions,
489
+ format: prompt.format,
490
+ style: this.mapStyleToProviderStyle(prompt.style),
491
+ numVariants: brief.variants,
492
+ seed: brief.seed
493
+ });
494
+ project.results = results;
495
+ } catch (error) {
496
+ const message = this.i18n.t("image.error.generationFailed");
497
+ throw new Error(message, error instanceof Error ? { cause: error } : undefined);
498
+ }
499
+ }
500
+ return project;
501
+ }
502
+ generateId(brief) {
503
+ const slug = brief.content.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40);
504
+ const ts = this.clock.now().toString(36);
505
+ return `img-${slug}-${ts}`;
506
+ }
507
+ mapStyleToProviderStyle(style) {
508
+ const providerStyles = [
509
+ "photorealistic",
510
+ "illustration",
511
+ "3d-render",
512
+ "flat-design",
513
+ "abstract"
514
+ ];
515
+ if (providerStyles.includes(style)) {
516
+ return style;
517
+ }
518
+ if (style === "minimalist" || style === "branded") {
519
+ return "flat-design";
520
+ }
521
+ return;
522
+ }
523
+ }
524
+ // src/presets/social.ts
525
+ function ogImageBrief(content) {
526
+ return {
527
+ content,
528
+ purpose: "social-og",
529
+ dimensions: IMAGE_PRESETS.ogImage,
530
+ format: "png",
531
+ style: "photorealistic"
532
+ };
533
+ }
534
+ function twitterCardBrief(content) {
535
+ return {
536
+ content,
537
+ purpose: "social-twitter",
538
+ dimensions: IMAGE_PRESETS.twitterCard,
539
+ format: "png",
540
+ style: "photorealistic"
541
+ };
542
+ }
543
+ function instagramSquareBrief(content) {
544
+ return {
545
+ content,
546
+ purpose: "social-instagram",
547
+ dimensions: IMAGE_PRESETS.instagramSquare,
548
+ format: "jpg",
549
+ style: "photorealistic"
550
+ };
551
+ }
552
+ function instagramStoryBrief(content) {
553
+ return {
554
+ content,
555
+ purpose: "social-instagram",
556
+ dimensions: IMAGE_PRESETS.instagramStory,
557
+ format: "jpg",
558
+ style: "photorealistic"
559
+ };
560
+ }
561
+
562
+ // src/presets/marketing.ts
563
+ function blogHeroBrief(content) {
564
+ return {
565
+ content,
566
+ purpose: "blog-hero",
567
+ dimensions: IMAGE_PRESETS.blogHero,
568
+ format: "webp",
569
+ style: "photorealistic"
570
+ };
571
+ }
572
+ function landingHeroBrief(content) {
573
+ return {
574
+ content,
575
+ purpose: "landing-hero",
576
+ dimensions: IMAGE_PRESETS.blogHero,
577
+ format: "webp",
578
+ style: "photorealistic"
579
+ };
580
+ }
581
+ function emailHeaderBrief(content) {
582
+ return {
583
+ content,
584
+ purpose: "email-header",
585
+ dimensions: IMAGE_PRESETS.emailHeader,
586
+ format: "png",
587
+ style: "flat-design"
588
+ };
589
+ }
590
+
591
+ // src/presets/video.ts
592
+ function videoThumbnailBrief(content) {
593
+ return {
594
+ content,
595
+ purpose: "video-thumbnail",
596
+ dimensions: IMAGE_PRESETS.thumbnail,
597
+ format: "png",
598
+ style: "photorealistic"
599
+ };
600
+ }
601
+ function videoThumbnailWideBrief(content) {
602
+ return {
603
+ content,
604
+ purpose: "video-thumbnail",
605
+ dimensions: { width: 1280, height: 720 },
606
+ format: "png",
607
+ style: "photorealistic"
608
+ };
609
+ }
610
+ export {
611
+ videoThumbnailWideBrief,
612
+ videoThumbnailBrief,
613
+ twitterCardBrief,
614
+ ogImageBrief,
615
+ landingHeroBrief,
616
+ instagramStoryBrief,
617
+ instagramSquareBrief,
618
+ emailHeaderBrief,
619
+ blogHeroBrief,
620
+ StyleResolver,
621
+ PromptBuilder,
622
+ ImageGenerator,
623
+ IMAGE_PRESETS
624
+ };
@@ -0,0 +1,36 @@
1
+ // src/docs/generators.docblock.ts
2
+ import { registerDocBlocks } from "@contractspec/lib.contracts-spec/docs";
3
+ var generatorDocBlocks = [
4
+ {
5
+ id: "docs.image-gen.generators",
6
+ title: "Image Generators",
7
+ summary: "Core generator classes: ImageGenerator, PromptBuilder, and StyleResolver.",
8
+ kind: "reference",
9
+ visibility: "public",
10
+ route: "/docs/image-gen/generators",
11
+ tags: ["image", "generator", "prompt", "style"],
12
+ owners: ["@contractspec/lib.image-gen"],
13
+ body: `# Image Generators
14
+
15
+ ## ImageGenerator
16
+
17
+ The main orchestrator. Accepts an \`ImageBrief\` and produces an \`ImageProject\`.
18
+
19
+ 1. **StyleResolver.resolve()** — resolves dimensions, style tokens, negative tokens
20
+ 2. **PromptBuilder.build()** — constructs the image prompt (deterministic or LLM)
21
+ 3. **ImageProvider.generate()** — calls the image generation provider (optional)
22
+
23
+ ## PromptBuilder
24
+
25
+ Supports two modes:
26
+ - **Deterministic**: Assembles a prompt from brief fields and style tokens
27
+ - **LLM-enhanced**: Sends a system prompt + brief JSON to the LLM, falls back to deterministic
28
+
29
+ ## StyleResolver
30
+
31
+ Maps \`ImagePurpose\` to default dimensions from \`IMAGE_PRESETS\`, and \`ImageStyle\` to
32
+ prompt tokens (e.g. "professional photography", "clean flat vector").
33
+ `
34
+ }
35
+ ];
36
+ registerDocBlocks(generatorDocBlocks);
@@ -0,0 +1,47 @@
1
+ // src/docs/image-gen.docblock.ts
2
+ import { registerDocBlocks } from "@contractspec/lib.contracts-spec/docs";
3
+ var imageGenDocBlocks = [
4
+ {
5
+ id: "docs.image-gen.overview",
6
+ title: "Image Generation Library",
7
+ summary: "AI-powered image generation for hero, social, thumbnail, OG, and illustration assets.",
8
+ kind: "reference",
9
+ visibility: "public",
10
+ route: "/docs/image-gen/overview",
11
+ tags: ["image", "generation", "ai", "content"],
12
+ owners: ["@contractspec/lib.image-gen"],
13
+ body: `# Image Generation Library
14
+
15
+ \`@contractspec/lib.image-gen\` provides deterministic and LLM-enhanced image prompt generation
16
+ for content pipelines. It supports multiple purposes (blog hero, social OG, thumbnail, etc.)
17
+ and styles (photorealistic, illustration, flat-design, etc.).
18
+
19
+ ## Key Features
20
+
21
+ - **Deterministic mode**: Generates structured prompts without an LLM
22
+ - **LLM-enhanced mode**: Uses an LLM to craft optimized image prompts
23
+ - **Style resolution**: Maps purposes to dimensions and style tokens
24
+ - **Preset briefs**: Ready-made brief constructors for common use cases
25
+ - **i18n support**: Localized strings for EN, FR, ES
26
+
27
+ ## Quick Start
28
+
29
+ \`\`\`typescript
30
+ import { ImageGenerator } from '@contractspec/lib.image-gen/generators';
31
+ import { blogHeroBrief } from '@contractspec/lib.image-gen/presets';
32
+
33
+ const generator = new ImageGenerator();
34
+ const brief = blogHeroBrief(contentBrief);
35
+ const project = await generator.generate(brief);
36
+ \`\`\`
37
+
38
+ ## Architecture
39
+
40
+ - **ImageGenerator** — orchestrates style resolution, prompt building, and provider calls
41
+ - **StyleResolver** — maps purpose + style to dimensions, tokens, and negative tokens
42
+ - **PromptBuilder** — constructs deterministic or LLM-enhanced image prompts
43
+ - **Presets** — convenience functions for social, marketing, and video thumbnails
44
+ `
45
+ }
46
+ ];
47
+ registerDocBlocks(imageGenDocBlocks);