@djangocfg/imgai 2.1.41 → 2.1.43

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/cli/index.js CHANGED
@@ -1,257 +1,14 @@
1
1
  import chalk from 'chalk';
2
2
  import inquirer from 'inquirer';
3
3
  import ora from 'ora';
4
- import fs from 'fs-extra';
5
- import path2 from 'path';
6
- import sharp from 'sharp';
4
+ import fs3 from 'fs-extra';
5
+ import path from 'path';
6
+ import { glob } from 'glob';
7
+ import sharp2 from 'sharp';
7
8
  import OpenAI from 'openai';
8
9
  import Anthropic from '@anthropic-ai/sdk';
9
- import { glob } from 'glob';
10
10
 
11
11
  // src/cli/index.ts
12
- var ImageGenerator = class {
13
- config;
14
- openai;
15
- anthropic;
16
- constructor(config) {
17
- this.config = config;
18
- this.initializeProviders();
19
- }
20
- initializeProviders() {
21
- const openaiKey = this.config.openaiApiKey || process.env.OPENAI_API_KEY;
22
- const anthropicKey = this.config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
23
- if (openaiKey) {
24
- this.openai = new OpenAI({ apiKey: openaiKey });
25
- }
26
- if (anthropicKey) {
27
- this.anthropic = new Anthropic({ apiKey: anthropicKey });
28
- }
29
- if (!this.openai && this.config.provider === "openai") {
30
- throw new Error("OpenAI API key required. Set OPENAI_API_KEY or pass openaiApiKey in config.");
31
- }
32
- }
33
- // ──────────────────────────────────────────────────────────────────────────
34
- // SINGLE IMAGE GENERATION
35
- // ──────────────────────────────────────────────────────────────────────────
36
- async generate(options) {
37
- const startTime = Date.now();
38
- try {
39
- const fullPrompt = this.buildPrompt(options.prompt);
40
- const imageData = await this.generateWithOpenAI(fullPrompt);
41
- const filename = options.filename || this.generateFilename(options.prompt);
42
- const category = options.category || "general";
43
- const outputDir = path2.join(
44
- this.config.projectRoot,
45
- this.config.outputDir,
46
- category
47
- );
48
- await fs.ensureDir(outputDir);
49
- const originalPath = path2.join(outputDir, `${filename}.png`);
50
- await fs.writeFile(originalPath, Buffer.from(imageData, "base64"));
51
- const resizeOptions = options.resize || this.config.resize;
52
- let finalPath = originalPath;
53
- if (resizeOptions) {
54
- finalPath = await this.resizeImage(originalPath, resizeOptions);
55
- if (finalPath !== originalPath) {
56
- await fs.remove(originalPath);
57
- }
58
- }
59
- const imageInfo = await this.getImageInfo(finalPath, category);
60
- imageInfo.metadata = {
61
- prompt: options.prompt,
62
- caption: options.metadata?.caption,
63
- tags: options.tags,
64
- category,
65
- generatedAt: /* @__PURE__ */ new Date(),
66
- model: "dall-e-3"
67
- };
68
- const duration = Date.now() - startTime;
69
- return {
70
- success: true,
71
- imagePath: finalPath,
72
- imageUrl: imageInfo.url,
73
- imageInfo,
74
- duration
75
- };
76
- } catch (error) {
77
- return {
78
- success: false,
79
- error: error instanceof Error ? error.message : "Unknown error",
80
- duration: Date.now() - startTime
81
- };
82
- }
83
- }
84
- // ──────────────────────────────────────────────────────────────────────────
85
- // BATCH GENERATION
86
- // ──────────────────────────────────────────────────────────────────────────
87
- async generateBatch(options) {
88
- const startTime = Date.now();
89
- const results = [];
90
- const concurrency = options.concurrency || 2;
91
- const delayBetween = options.delayBetween || 2e3;
92
- for (let i = 0; i < options.items.length; i += concurrency) {
93
- const batch = options.items.slice(i, i + concurrency);
94
- const batchPromises = batch.map(async (item) => {
95
- const result = await this.generate({
96
- prompt: item.prompt,
97
- filename: item.filename,
98
- category: item.category
99
- });
100
- return result;
101
- });
102
- const batchResults = await Promise.all(batchPromises);
103
- for (const result of batchResults) {
104
- results.push(result);
105
- if (options.onProgress) {
106
- options.onProgress(results.length, options.items.length, result);
107
- }
108
- }
109
- if (i + concurrency < options.items.length) {
110
- await this.delay(delayBetween);
111
- }
112
- }
113
- return {
114
- total: results.length,
115
- success: results.filter((r) => r.success).length,
116
- failed: results.filter((r) => !r.success).length,
117
- results,
118
- duration: Date.now() - startTime
119
- };
120
- }
121
- // ──────────────────────────────────────────────────────────────────────────
122
- // PROMPT ENHANCEMENT (using Claude/GPT)
123
- // ──────────────────────────────────────────────────────────────────────────
124
- async enhancePrompt(basePrompt, context) {
125
- const systemPrompt = `You are an expert at creating image generation prompts.
126
- Given a description, create:
127
- 1. A detailed visual prompt (max 150 words) for DALL-E 3
128
- 2. A descriptive filename (lowercase, hyphens, max 30 chars)
129
- 3. A short caption (max 50 words)
130
-
131
- ${this.config.prefix ? `Style prefix: ${this.config.prefix}` : ""}
132
- ${this.config.suffix ? `Style suffix: ${this.config.suffix}` : ""}
133
-
134
- Return ONLY valid JSON: {"prompt": "...", "filename": "...", "caption": "..."}`;
135
- const userPrompt = context ? `Description: ${basePrompt}
136
- Context: ${context}` : `Description: ${basePrompt}`;
137
- if (this.anthropic && this.config.provider === "anthropic") {
138
- const response = await this.anthropic.messages.create({
139
- model: "claude-3-5-sonnet-20241022",
140
- max_tokens: 500,
141
- messages: [
142
- { role: "user", content: `${systemPrompt}
143
-
144
- ${userPrompt}` }
145
- ]
146
- });
147
- const text = response.content[0].type === "text" ? response.content[0].text : "";
148
- return JSON.parse(text);
149
- }
150
- if (this.openai) {
151
- const response = await this.openai.chat.completions.create({
152
- model: "gpt-4-turbo-preview",
153
- messages: [
154
- { role: "system", content: systemPrompt },
155
- { role: "user", content: userPrompt }
156
- ],
157
- max_tokens: 500,
158
- temperature: 0.7,
159
- response_format: { type: "json_object" }
160
- });
161
- const text = response.choices[0]?.message?.content || "{}";
162
- return JSON.parse(text);
163
- }
164
- throw new Error("No AI provider configured for prompt enhancement");
165
- }
166
- // ──────────────────────────────────────────────────────────────────────────
167
- // PRIVATE METHODS
168
- // ──────────────────────────────────────────────────────────────────────────
169
- async generateWithOpenAI(prompt) {
170
- if (!this.openai) {
171
- throw new Error("OpenAI client not initialized");
172
- }
173
- const response = await this.openai.images.generate({
174
- model: "dall-e-3",
175
- prompt,
176
- n: 1,
177
- size: this.config.size,
178
- quality: this.config.quality,
179
- style: this.config.dalleStyle || "natural",
180
- response_format: "b64_json"
181
- });
182
- const imageData = response.data?.[0]?.b64_json;
183
- if (!imageData) {
184
- throw new Error("No image data received from OpenAI");
185
- }
186
- return imageData;
187
- }
188
- async resizeImage(inputPath, options) {
189
- const { width, height, quality = 85, format = "webp", fit = "inside" } = options;
190
- let pipeline = sharp(inputPath);
191
- if (width || height) {
192
- pipeline = pipeline.resize(width, height, {
193
- fit,
194
- withoutEnlargement: true
195
- });
196
- }
197
- const outputPath = inputPath.replace(/\.[^.]+$/, `.${format}`);
198
- switch (format) {
199
- case "webp":
200
- pipeline = pipeline.webp({ quality });
201
- break;
202
- case "jpeg":
203
- pipeline = pipeline.jpeg({ quality });
204
- break;
205
- case "png":
206
- pipeline = pipeline.png({ quality });
207
- break;
208
- case "avif":
209
- pipeline = pipeline.avif({ quality });
210
- break;
211
- }
212
- await pipeline.toFile(outputPath);
213
- return outputPath;
214
- }
215
- async getImageInfo(imagePath, category) {
216
- const stats = await fs.stat(imagePath);
217
- const metadata = await sharp(imagePath).metadata();
218
- const filename = path2.basename(imagePath);
219
- const extension = path2.extname(filename).slice(1);
220
- const id = path2.basename(filename, path2.extname(filename));
221
- const publicDir = path2.join(this.config.projectRoot, this.config.publicDir);
222
- const relativePath = path2.relative(publicDir, imagePath);
223
- const url = "/" + relativePath.replace(/\\/g, "/");
224
- return {
225
- id,
226
- filename,
227
- extension,
228
- path: relativePath,
229
- url,
230
- size: stats.size,
231
- width: metadata.width,
232
- height: metadata.height,
233
- createdAt: stats.birthtime,
234
- modifiedAt: stats.mtime
235
- };
236
- }
237
- buildPrompt(basePrompt) {
238
- const parts = [];
239
- if (this.config.prefix) {
240
- parts.push(this.config.prefix);
241
- }
242
- parts.push(basePrompt);
243
- if (this.config.suffix) {
244
- parts.push(this.config.suffix);
245
- }
246
- return parts.join(" ");
247
- }
248
- generateFilename(prompt) {
249
- return prompt.toLowerCase().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, "-").substring(0, 30);
250
- }
251
- delay(ms) {
252
- return new Promise((resolve) => setTimeout(resolve, ms));
253
- }
254
- };
255
12
  var DEFAULT_EXTENSIONS = ["png", "jpg", "jpeg", "webp", "avif", "gif", "svg"];
256
13
  var DEFAULT_DIRECTORIES = ["static/images", "images", "assets"];
257
14
  var ImageScanner = class {
@@ -270,15 +27,15 @@ var ImageScanner = class {
270
27
  includeDimensions = true,
271
28
  recursive = true
272
29
  } = options;
273
- const publicDir = path2.join(this.config.projectRoot, this.config.publicDir);
30
+ const publicDir = path.join(this.config.projectRoot, this.config.publicDir);
274
31
  const images = [];
275
32
  const byCategory = {};
276
33
  const byExtension = {};
277
34
  let totalSize = 0;
278
35
  const patterns = directories.flatMap((dir) => {
279
- const basePath = path2.join(publicDir, dir);
36
+ const basePath = path.join(publicDir, dir);
280
37
  const extPattern = `*.{${extensions.join(",")}}`;
281
- return recursive ? [path2.join(basePath, "**", extPattern)] : [path2.join(basePath, extPattern)];
38
+ return recursive ? [path.join(basePath, "**", extPattern)] : [path.join(basePath, extPattern)];
282
39
  });
283
40
  for (const pattern of patterns) {
284
41
  const files = await glob(pattern, { nodir: true });
@@ -345,17 +102,17 @@ var ImageScanner = class {
345
102
  // PRIVATE METHODS
346
103
  // ──────────────────────────────────────────────────────────────────────────
347
104
  async processImage(filePath, publicDir, includeDimensions) {
348
- const stats = await fs.stat(filePath);
349
- const filename = path2.basename(filePath);
350
- const extension = path2.extname(filename).slice(1).toLowerCase();
351
- const id = path2.basename(filename, path2.extname(filename));
352
- const relativePath = path2.relative(publicDir, filePath).replace(/\\/g, "/");
105
+ const stats = await fs3.stat(filePath);
106
+ const filename = path.basename(filePath);
107
+ const extension = path.extname(filename).slice(1).toLowerCase();
108
+ const id = path.basename(filename, path.extname(filename));
109
+ const relativePath = path.relative(publicDir, filePath).replace(/\\/g, "/");
353
110
  const url = "/" + relativePath;
354
111
  let width;
355
112
  let height;
356
113
  if (includeDimensions && extension !== "svg") {
357
114
  try {
358
- const metadata = await sharp(filePath).metadata();
115
+ const metadata = await sharp2(filePath).metadata();
359
116
  width = metadata.width;
360
117
  height = metadata.height;
361
118
  } catch {
@@ -393,6 +150,8 @@ function formatDuration(ms) {
393
150
  if (ms < 1e3) return `${ms}ms`;
394
151
  return `${(ms / 1e3).toFixed(2)}s`;
395
152
  }
153
+
154
+ // src/config/generator.ts
396
155
  var ConfigGenerator = class {
397
156
  config;
398
157
  scanner;
@@ -405,10 +164,10 @@ var ConfigGenerator = class {
405
164
  // ──────────────────────────────────────────────────────────────────────────
406
165
  async generate() {
407
166
  const catalog = await this.scanner.buildCatalog();
408
- const outputPath = path2.join(this.config.projectRoot, this.config.configOutputPath);
409
- await fs.ensureDir(path2.dirname(outputPath));
167
+ const outputPath = path.join(this.config.projectRoot, this.config.configOutputPath);
168
+ await fs3.ensureDir(path.dirname(outputPath));
410
169
  const content = this.generateTypeScript(catalog);
411
- await fs.writeFile(outputPath, content, "utf-8");
170
+ await fs3.writeFile(outputPath, content, "utf-8");
412
171
  return outputPath;
413
172
  }
414
173
  // ──────────────────────────────────────────────────────────────────────────
@@ -524,6 +283,249 @@ export const IMAGE_CATALOG_META = {
524
283
  return ` '${category}': [${imageRefs.join(", ")}]`;
525
284
  }
526
285
  };
286
+ var ImageGenerator = class {
287
+ config;
288
+ openai;
289
+ anthropic;
290
+ constructor(config) {
291
+ this.config = config;
292
+ this.initializeProviders();
293
+ }
294
+ initializeProviders() {
295
+ const openaiKey = this.config.openaiApiKey || process.env.OPENAI_API_KEY;
296
+ const anthropicKey = this.config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
297
+ if (openaiKey) {
298
+ this.openai = new OpenAI({ apiKey: openaiKey });
299
+ }
300
+ if (anthropicKey) {
301
+ this.anthropic = new Anthropic({ apiKey: anthropicKey });
302
+ }
303
+ if (!this.openai && this.config.provider === "openai") {
304
+ throw new Error("OpenAI API key required. Set OPENAI_API_KEY or pass openaiApiKey in config.");
305
+ }
306
+ }
307
+ // ──────────────────────────────────────────────────────────────────────────
308
+ // SINGLE IMAGE GENERATION
309
+ // ──────────────────────────────────────────────────────────────────────────
310
+ async generate(options) {
311
+ const startTime = Date.now();
312
+ try {
313
+ const fullPrompt = this.buildPrompt(options.prompt);
314
+ const imageData = await this.generateWithOpenAI(fullPrompt);
315
+ const filename = options.filename || this.generateFilename(options.prompt);
316
+ const category = options.category || "general";
317
+ const outputDir = path.join(
318
+ this.config.projectRoot,
319
+ this.config.outputDir,
320
+ category
321
+ );
322
+ await fs3.ensureDir(outputDir);
323
+ const originalPath = path.join(outputDir, `${filename}.png`);
324
+ await fs3.writeFile(originalPath, Buffer.from(imageData, "base64"));
325
+ const resizeOptions = options.resize || this.config.resize;
326
+ let finalPath = originalPath;
327
+ if (resizeOptions) {
328
+ finalPath = await this.resizeImage(originalPath, resizeOptions);
329
+ if (finalPath !== originalPath) {
330
+ await fs3.remove(originalPath);
331
+ }
332
+ }
333
+ const imageInfo = await this.getImageInfo(finalPath, category);
334
+ imageInfo.metadata = {
335
+ prompt: options.prompt,
336
+ caption: options.metadata?.caption,
337
+ tags: options.tags,
338
+ category,
339
+ generatedAt: /* @__PURE__ */ new Date(),
340
+ model: "dall-e-3"
341
+ };
342
+ const duration = Date.now() - startTime;
343
+ return {
344
+ success: true,
345
+ imagePath: finalPath,
346
+ imageUrl: imageInfo.url,
347
+ imageInfo,
348
+ duration
349
+ };
350
+ } catch (error) {
351
+ return {
352
+ success: false,
353
+ error: error instanceof Error ? error.message : "Unknown error",
354
+ duration: Date.now() - startTime
355
+ };
356
+ }
357
+ }
358
+ // ──────────────────────────────────────────────────────────────────────────
359
+ // BATCH GENERATION
360
+ // ──────────────────────────────────────────────────────────────────────────
361
+ async generateBatch(options) {
362
+ const startTime = Date.now();
363
+ const results = [];
364
+ const concurrency = options.concurrency || 2;
365
+ const delayBetween = options.delayBetween || 2e3;
366
+ for (let i = 0; i < options.items.length; i += concurrency) {
367
+ const batch = options.items.slice(i, i + concurrency);
368
+ const batchPromises = batch.map(async (item) => {
369
+ const result = await this.generate({
370
+ prompt: item.prompt,
371
+ filename: item.filename,
372
+ category: item.category
373
+ });
374
+ return result;
375
+ });
376
+ const batchResults = await Promise.all(batchPromises);
377
+ for (const result of batchResults) {
378
+ results.push(result);
379
+ if (options.onProgress) {
380
+ options.onProgress(results.length, options.items.length, result);
381
+ }
382
+ }
383
+ if (i + concurrency < options.items.length) {
384
+ await this.delay(delayBetween);
385
+ }
386
+ }
387
+ return {
388
+ total: results.length,
389
+ success: results.filter((r) => r.success).length,
390
+ failed: results.filter((r) => !r.success).length,
391
+ results,
392
+ duration: Date.now() - startTime
393
+ };
394
+ }
395
+ // ──────────────────────────────────────────────────────────────────────────
396
+ // PROMPT ENHANCEMENT (using Claude/GPT)
397
+ // ──────────────────────────────────────────────────────────────────────────
398
+ async enhancePrompt(basePrompt, context) {
399
+ const systemPrompt = `You are an expert at creating image generation prompts.
400
+ Given a description, create:
401
+ 1. A detailed visual prompt (max 150 words) for DALL-E 3
402
+ 2. A descriptive filename (lowercase, hyphens, max 30 chars)
403
+ 3. A short caption (max 50 words)
404
+
405
+ ${this.config.prefix ? `Style prefix: ${this.config.prefix}` : ""}
406
+ ${this.config.suffix ? `Style suffix: ${this.config.suffix}` : ""}
407
+
408
+ Return ONLY valid JSON: {"prompt": "...", "filename": "...", "caption": "..."}`;
409
+ const userPrompt = context ? `Description: ${basePrompt}
410
+ Context: ${context}` : `Description: ${basePrompt}`;
411
+ if (this.anthropic && this.config.provider === "anthropic") {
412
+ const response = await this.anthropic.messages.create({
413
+ model: "claude-3-5-sonnet-20241022",
414
+ max_tokens: 500,
415
+ messages: [
416
+ { role: "user", content: `${systemPrompt}
417
+
418
+ ${userPrompt}` }
419
+ ]
420
+ });
421
+ const text = response.content[0].type === "text" ? response.content[0].text : "";
422
+ return JSON.parse(text);
423
+ }
424
+ if (this.openai) {
425
+ const response = await this.openai.chat.completions.create({
426
+ model: "gpt-4-turbo-preview",
427
+ messages: [
428
+ { role: "system", content: systemPrompt },
429
+ { role: "user", content: userPrompt }
430
+ ],
431
+ max_tokens: 500,
432
+ temperature: 0.7,
433
+ response_format: { type: "json_object" }
434
+ });
435
+ const text = response.choices[0]?.message?.content || "{}";
436
+ return JSON.parse(text);
437
+ }
438
+ throw new Error("No AI provider configured for prompt enhancement");
439
+ }
440
+ // ──────────────────────────────────────────────────────────────────────────
441
+ // PRIVATE METHODS
442
+ // ──────────────────────────────────────────────────────────────────────────
443
+ async generateWithOpenAI(prompt) {
444
+ if (!this.openai) {
445
+ throw new Error("OpenAI client not initialized");
446
+ }
447
+ const response = await this.openai.images.generate({
448
+ model: "dall-e-3",
449
+ prompt,
450
+ n: 1,
451
+ size: this.config.size,
452
+ quality: this.config.quality,
453
+ style: this.config.dalleStyle || "natural",
454
+ response_format: "b64_json"
455
+ });
456
+ const imageData = response.data?.[0]?.b64_json;
457
+ if (!imageData) {
458
+ throw new Error("No image data received from OpenAI");
459
+ }
460
+ return imageData;
461
+ }
462
+ async resizeImage(inputPath, options) {
463
+ const { width, height, quality = 85, format = "webp", fit = "inside" } = options;
464
+ let pipeline = sharp2(inputPath);
465
+ if (width || height) {
466
+ pipeline = pipeline.resize(width, height, {
467
+ fit,
468
+ withoutEnlargement: true
469
+ });
470
+ }
471
+ const outputPath = inputPath.replace(/\.[^.]+$/, `.${format}`);
472
+ switch (format) {
473
+ case "webp":
474
+ pipeline = pipeline.webp({ quality });
475
+ break;
476
+ case "jpeg":
477
+ pipeline = pipeline.jpeg({ quality });
478
+ break;
479
+ case "png":
480
+ pipeline = pipeline.png({ quality });
481
+ break;
482
+ case "avif":
483
+ pipeline = pipeline.avif({ quality });
484
+ break;
485
+ }
486
+ await pipeline.toFile(outputPath);
487
+ return outputPath;
488
+ }
489
+ async getImageInfo(imagePath, category) {
490
+ const stats = await fs3.stat(imagePath);
491
+ const metadata = await sharp2(imagePath).metadata();
492
+ const filename = path.basename(imagePath);
493
+ const extension = path.extname(filename).slice(1);
494
+ const id = path.basename(filename, path.extname(filename));
495
+ const publicDir = path.join(this.config.projectRoot, this.config.publicDir);
496
+ const relativePath = path.relative(publicDir, imagePath);
497
+ const url = "/" + relativePath.replace(/\\/g, "/");
498
+ return {
499
+ id,
500
+ filename,
501
+ extension,
502
+ path: relativePath,
503
+ url,
504
+ size: stats.size,
505
+ width: metadata.width,
506
+ height: metadata.height,
507
+ createdAt: stats.birthtime,
508
+ modifiedAt: stats.mtime
509
+ };
510
+ }
511
+ buildPrompt(basePrompt) {
512
+ const parts = [];
513
+ if (this.config.prefix) {
514
+ parts.push(this.config.prefix);
515
+ }
516
+ parts.push(basePrompt);
517
+ if (this.config.suffix) {
518
+ parts.push(this.config.suffix);
519
+ }
520
+ return parts.join(" ");
521
+ }
522
+ generateFilename(prompt) {
523
+ return prompt.toLowerCase().replace(/[^a-z0-9\s]/g, "").replace(/\s+/g, "-").substring(0, 30);
524
+ }
525
+ delay(ms) {
526
+ return new Promise((resolve) => setTimeout(resolve, ms));
527
+ }
528
+ };
527
529
 
528
530
  // src/cli/index.ts
529
531
  var ImageAICLI = class {