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