@djangocfg/imgai 2.1.42 → 2.1.44
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.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
|
|
5
|
-
import
|
|
6
|
-
import
|
|
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 =
|
|
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 =
|
|
36
|
+
const basePath = path.join(publicDir, dir);
|
|
280
37
|
const extPattern = `*.{${extensions.join(",")}}`;
|
|
281
|
-
return recursive ? [
|
|
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
|
|
349
|
-
const filename =
|
|
350
|
-
const extension =
|
|
351
|
-
const id =
|
|
352
|
-
const relativePath =
|
|
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
|
|
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 =
|
|
409
|
-
await
|
|
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
|
|
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 {
|