@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.
- package/dist/browser/docs/generators.docblock.js +36 -0
- package/dist/browser/docs/image-gen.docblock.js +47 -0
- package/dist/browser/generators/image-generator.js +525 -0
- package/dist/browser/generators/index.js +527 -0
- package/dist/browser/generators/prompt-builder.js +93 -0
- package/dist/browser/generators/style-resolver.js +90 -0
- package/dist/browser/i18n/catalogs/en.js +85 -0
- package/dist/browser/i18n/catalogs/es.js +85 -0
- package/dist/browser/i18n/catalogs/fr.js +85 -0
- package/dist/browser/i18n/catalogs/index.js +253 -0
- package/dist/browser/i18n/index.js +309 -0
- package/dist/browser/i18n/keys.js +33 -0
- package/dist/browser/i18n/locale.js +13 -0
- package/dist/browser/i18n/messages.js +265 -0
- package/dist/browser/index.js +623 -0
- package/dist/browser/presets/index.js +99 -0
- package/dist/browser/presets/marketing.js +35 -0
- package/dist/browser/presets/social.js +45 -0
- package/dist/browser/presets/video.js +25 -0
- package/dist/browser/types.js +5 -0
- package/dist/docs/generators.docblock.d.ts +1 -0
- package/dist/docs/generators.docblock.js +37 -0
- package/dist/docs/image-gen.docblock.d.ts +1 -0
- package/dist/docs/image-gen.docblock.js +48 -0
- package/dist/generators/image-generator.d.ts +12 -0
- package/dist/generators/image-generator.js +526 -0
- package/dist/generators/image-generator.test.d.ts +1 -0
- package/dist/generators/index.d.ts +4 -0
- package/dist/generators/index.js +528 -0
- package/dist/generators/prompt-builder.d.ts +21 -0
- package/dist/generators/prompt-builder.js +94 -0
- package/dist/generators/prompt-builder.test.d.ts +1 -0
- package/dist/generators/style-resolver.d.ts +9 -0
- package/dist/generators/style-resolver.js +91 -0
- package/dist/generators/style-resolver.test.d.ts +1 -0
- package/dist/i18n/catalogs/en.d.ts +8 -0
- package/dist/i18n/catalogs/en.js +86 -0
- package/dist/i18n/catalogs/es.d.ts +6 -0
- package/dist/i18n/catalogs/es.js +86 -0
- package/dist/i18n/catalogs/fr.d.ts +6 -0
- package/dist/i18n/catalogs/fr.js +86 -0
- package/dist/i18n/catalogs/index.d.ts +3 -0
- package/dist/i18n/catalogs/index.js +254 -0
- package/dist/i18n/i18n.test.d.ts +1 -0
- package/dist/i18n/index.d.ts +6 -0
- package/dist/i18n/index.js +310 -0
- package/dist/i18n/keys.d.ts +78 -0
- package/dist/i18n/keys.js +34 -0
- package/dist/i18n/locale.d.ts +8 -0
- package/dist/i18n/locale.js +14 -0
- package/dist/i18n/messages.d.ts +14 -0
- package/dist/i18n/messages.js +266 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +624 -0
- package/dist/node/docs/generators.docblock.js +36 -0
- package/dist/node/docs/image-gen.docblock.js +47 -0
- package/dist/node/generators/image-generator.js +525 -0
- package/dist/node/generators/index.js +527 -0
- package/dist/node/generators/prompt-builder.js +93 -0
- package/dist/node/generators/style-resolver.js +90 -0
- package/dist/node/i18n/catalogs/en.js +85 -0
- package/dist/node/i18n/catalogs/es.js +85 -0
- package/dist/node/i18n/catalogs/fr.js +85 -0
- package/dist/node/i18n/catalogs/index.js +253 -0
- package/dist/node/i18n/index.js +309 -0
- package/dist/node/i18n/keys.js +33 -0
- package/dist/node/i18n/locale.js +13 -0
- package/dist/node/i18n/messages.js +265 -0
- package/dist/node/index.js +623 -0
- package/dist/node/presets/index.js +99 -0
- package/dist/node/presets/marketing.js +35 -0
- package/dist/node/presets/social.js +45 -0
- package/dist/node/presets/video.js +25 -0
- package/dist/node/types.js +5 -0
- package/dist/presets/index.d.ts +3 -0
- package/dist/presets/index.js +100 -0
- package/dist/presets/marketing.d.ts +8 -0
- package/dist/presets/marketing.js +36 -0
- package/dist/presets/presets.test.d.ts +1 -0
- package/dist/presets/social.d.ts +10 -0
- package/dist/presets/social.js +46 -0
- package/dist/presets/video.d.ts +6 -0
- package/dist/presets/video.js +26 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +6 -0
- package/package.json +394 -0
|
@@ -0,0 +1,528 @@
|
|
|
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
|
+
export {
|
|
525
|
+
StyleResolver,
|
|
526
|
+
PromptBuilder,
|
|
527
|
+
ImageGenerator
|
|
528
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { LLMProvider } from '@contractspec/lib.contracts-integrations/integrations/providers/llm';
|
|
2
|
+
import type { ImageBrief, ImagePrompt } from '../types';
|
|
3
|
+
import type { ResolvedStyle } from './style-resolver';
|
|
4
|
+
import type { ImageGenI18n } from '../i18n/messages';
|
|
5
|
+
interface PromptBuilderOptions {
|
|
6
|
+
llm?: LLMProvider;
|
|
7
|
+
model?: string;
|
|
8
|
+
temperature?: number;
|
|
9
|
+
i18n: ImageGenI18n;
|
|
10
|
+
}
|
|
11
|
+
export declare class PromptBuilder {
|
|
12
|
+
private readonly llm?;
|
|
13
|
+
private readonly model?;
|
|
14
|
+
private readonly temperature;
|
|
15
|
+
private readonly i18n;
|
|
16
|
+
constructor(options: PromptBuilderOptions);
|
|
17
|
+
build(brief: ImageBrief, resolvedStyle: ResolvedStyle): Promise<ImagePrompt>;
|
|
18
|
+
private buildWithLlm;
|
|
19
|
+
private buildDeterministic;
|
|
20
|
+
}
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/generators/prompt-builder.ts
|
|
3
|
+
class PromptBuilder {
|
|
4
|
+
llm;
|
|
5
|
+
model;
|
|
6
|
+
temperature;
|
|
7
|
+
i18n;
|
|
8
|
+
constructor(options) {
|
|
9
|
+
this.llm = options.llm;
|
|
10
|
+
this.model = options.model;
|
|
11
|
+
this.temperature = options.temperature ?? 0.4;
|
|
12
|
+
this.i18n = options.i18n;
|
|
13
|
+
}
|
|
14
|
+
async build(brief, resolvedStyle) {
|
|
15
|
+
const style = brief.style ?? "photorealistic";
|
|
16
|
+
const format = brief.format ?? "png";
|
|
17
|
+
if (this.llm) {
|
|
18
|
+
try {
|
|
19
|
+
return await this.buildWithLlm(brief, resolvedStyle, style, format);
|
|
20
|
+
} catch {
|
|
21
|
+
return this.buildDeterministic(brief, resolvedStyle, style, format);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return this.buildDeterministic(brief, resolvedStyle, style, format);
|
|
25
|
+
}
|
|
26
|
+
async buildWithLlm(brief, resolvedStyle, style, format) {
|
|
27
|
+
const systemPrompt = this.i18n.t("prompt.system.imagePromptEngineer");
|
|
28
|
+
const briefJson = JSON.stringify({
|
|
29
|
+
title: brief.content.title,
|
|
30
|
+
summary: brief.content.summary,
|
|
31
|
+
problems: brief.content.problems,
|
|
32
|
+
solutions: brief.content.solutions,
|
|
33
|
+
purpose: brief.purpose,
|
|
34
|
+
style,
|
|
35
|
+
styleTokens: resolvedStyle.styleTokens
|
|
36
|
+
});
|
|
37
|
+
const messages = [
|
|
38
|
+
{
|
|
39
|
+
role: "system",
|
|
40
|
+
content: [{ type: "text", text: systemPrompt }]
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
role: "user",
|
|
44
|
+
content: [{ type: "text", text: briefJson }]
|
|
45
|
+
}
|
|
46
|
+
];
|
|
47
|
+
const llm = this.llm;
|
|
48
|
+
if (!llm) {
|
|
49
|
+
throw new Error("LLM provider is required for buildWithLlm");
|
|
50
|
+
}
|
|
51
|
+
const response = await llm.chat(messages, {
|
|
52
|
+
model: this.model,
|
|
53
|
+
temperature: this.temperature
|
|
54
|
+
});
|
|
55
|
+
const text = response.message.content.filter((block) => block.type === "text").map((block) => ("text" in block) ? block.text : "").join("");
|
|
56
|
+
return {
|
|
57
|
+
text: text.trim(),
|
|
58
|
+
negativeText: resolvedStyle.negativeTokens.join(", "),
|
|
59
|
+
style,
|
|
60
|
+
dimensions: resolvedStyle.dimensions,
|
|
61
|
+
format
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
buildDeterministic(brief, resolvedStyle, style, format) {
|
|
65
|
+
const parts = [
|
|
66
|
+
brief.content.title,
|
|
67
|
+
this.i18n.t("image.generate.description", {
|
|
68
|
+
style,
|
|
69
|
+
purpose: brief.purpose
|
|
70
|
+
})
|
|
71
|
+
];
|
|
72
|
+
if (brief.content.solutions.length > 0) {
|
|
73
|
+
parts.push(this.i18n.t("image.prompt.featuring", {
|
|
74
|
+
solutions: brief.content.solutions.slice(0, 3).join(", ")
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
if (brief.content.audience?.industry) {
|
|
78
|
+
parts.push(this.i18n.t("image.prompt.industryContext", {
|
|
79
|
+
industry: brief.content.audience.industry
|
|
80
|
+
}));
|
|
81
|
+
}
|
|
82
|
+
parts.push(...resolvedStyle.styleTokens);
|
|
83
|
+
return {
|
|
84
|
+
text: parts.join(", "),
|
|
85
|
+
negativeText: resolvedStyle.negativeTokens.join(", "),
|
|
86
|
+
style,
|
|
87
|
+
dimensions: resolvedStyle.dimensions,
|
|
88
|
+
format
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
PromptBuilder
|
|
94
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ImageDimensions, ImagePurpose, ImageStyle, BrandColorOverrides } from '../types';
|
|
2
|
+
export interface ResolvedStyle {
|
|
3
|
+
styleTokens: string[];
|
|
4
|
+
negativeTokens: string[];
|
|
5
|
+
dimensions: ImageDimensions;
|
|
6
|
+
}
|
|
7
|
+
export declare class StyleResolver {
|
|
8
|
+
resolve(purpose: ImagePurpose, style?: ImageStyle, brand?: BrandColorOverrides): ResolvedStyle;
|
|
9
|
+
}
|