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