@nclamvn/vibecode-cli 2.2.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/vibecode.js +101 -2
- package/package.json +3 -1
- package/src/commands/config.js +42 -4
- package/src/commands/deploy.js +728 -0
- package/src/commands/favorite.js +412 -0
- package/src/commands/feedback.js +473 -0
- package/src/commands/go.js +170 -4
- package/src/commands/history.js +249 -0
- package/src/commands/images.js +465 -0
- package/src/commands/preview.js +554 -0
- package/src/commands/voice.js +580 -0
- package/src/commands/watch.js +3 -20
- package/src/index.js +49 -2
- package/src/services/image-service.js +513 -0
- package/src/utils/history.js +357 -0
- package/src/utils/notifications.js +343 -0
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Image Service
|
|
3
|
+
// AI-powered image generation and integration using Unsplash API
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import https from 'https';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
|
|
11
|
+
// Curated image collections for professional results
|
|
12
|
+
const CURATED_COLLECTIONS = {
|
|
13
|
+
hero: {
|
|
14
|
+
tech: [
|
|
15
|
+
'https://images.unsplash.com/photo-1518770660439-4636190af475?w=1920&q=80',
|
|
16
|
+
'https://images.unsplash.com/photo-1451187580459-43490279c0fa?w=1920&q=80',
|
|
17
|
+
'https://images.unsplash.com/photo-1550751827-4bd374c3f58b?w=1920&q=80',
|
|
18
|
+
'https://images.unsplash.com/photo-1526374965328-7f61d4dc18c5?w=1920&q=80',
|
|
19
|
+
'https://images.unsplash.com/photo-1504639725590-34d0984388bd?w=1920&q=80'
|
|
20
|
+
],
|
|
21
|
+
business: [
|
|
22
|
+
'https://images.unsplash.com/photo-1497366216548-37526070297c?w=1920&q=80',
|
|
23
|
+
'https://images.unsplash.com/photo-1497215728101-856f4ea42174?w=1920&q=80',
|
|
24
|
+
'https://images.unsplash.com/photo-1454165804606-c3d57bc86b40?w=1920&q=80',
|
|
25
|
+
'https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=1920&q=80',
|
|
26
|
+
'https://images.unsplash.com/photo-1552664730-d307ca884978?w=1920&q=80'
|
|
27
|
+
],
|
|
28
|
+
creative: [
|
|
29
|
+
'https://images.unsplash.com/photo-1499951360447-b19be8fe80f5?w=1920&q=80',
|
|
30
|
+
'https://images.unsplash.com/photo-1558655146-9f40138edfeb?w=1920&q=80',
|
|
31
|
+
'https://images.unsplash.com/photo-1542744094-3a31f272c490?w=1920&q=80',
|
|
32
|
+
'https://images.unsplash.com/photo-1516321318423-f06f85e504b3?w=1920&q=80',
|
|
33
|
+
'https://images.unsplash.com/photo-1460925895917-afdab827c52f?w=1920&q=80'
|
|
34
|
+
],
|
|
35
|
+
nature: [
|
|
36
|
+
'https://images.unsplash.com/photo-1469474968028-56623f02e42e?w=1920&q=80',
|
|
37
|
+
'https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=1920&q=80',
|
|
38
|
+
'https://images.unsplash.com/photo-1501854140801-50d01698950b?w=1920&q=80',
|
|
39
|
+
'https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=1920&q=80',
|
|
40
|
+
'https://images.unsplash.com/photo-1433086966358-54859d0ed716?w=1920&q=80'
|
|
41
|
+
],
|
|
42
|
+
abstract: [
|
|
43
|
+
'https://images.unsplash.com/photo-1557672172-298e090bd0f1?w=1920&q=80',
|
|
44
|
+
'https://images.unsplash.com/photo-1618005182384-a83a8bd57fbe?w=1920&q=80',
|
|
45
|
+
'https://images.unsplash.com/photo-1579546929518-9e396f3cc809?w=1920&q=80',
|
|
46
|
+
'https://images.unsplash.com/photo-1558591710-4b4a1ae0f04d?w=1920&q=80',
|
|
47
|
+
'https://images.unsplash.com/photo-1508615039623-a25605d2b022?w=1920&q=80'
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
products: {
|
|
51
|
+
tech: [
|
|
52
|
+
'https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=800&q=80',
|
|
53
|
+
'https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=800&q=80',
|
|
54
|
+
'https://images.unsplash.com/photo-1546868871-7041f2a55e12?w=800&q=80',
|
|
55
|
+
'https://images.unsplash.com/photo-1585386959984-a4155224a1ad?w=800&q=80',
|
|
56
|
+
'https://images.unsplash.com/photo-1491933382434-500287f9b54b?w=800&q=80',
|
|
57
|
+
'https://images.unsplash.com/photo-1560343090-f0409e92791a?w=800&q=80'
|
|
58
|
+
],
|
|
59
|
+
fashion: [
|
|
60
|
+
'https://images.unsplash.com/photo-1542291026-7eec264c27ff?w=800&q=80',
|
|
61
|
+
'https://images.unsplash.com/photo-1549298916-b41d501d3772?w=800&q=80',
|
|
62
|
+
'https://images.unsplash.com/photo-1441986300917-64674bd600d8?w=800&q=80',
|
|
63
|
+
'https://images.unsplash.com/photo-1556905055-8f358a7a47b2?w=800&q=80',
|
|
64
|
+
'https://images.unsplash.com/photo-1467043237213-65f2da53396f?w=800&q=80'
|
|
65
|
+
],
|
|
66
|
+
food: [
|
|
67
|
+
'https://images.unsplash.com/photo-1504674900247-0877df9cc836?w=800&q=80',
|
|
68
|
+
'https://images.unsplash.com/photo-1512621776951-a57141f2eefd?w=800&q=80',
|
|
69
|
+
'https://images.unsplash.com/photo-1565299624946-b28f40a0ae38?w=800&q=80',
|
|
70
|
+
'https://images.unsplash.com/photo-1482049016gy-d606572dc5de?w=800&q=80',
|
|
71
|
+
'https://images.unsplash.com/photo-1540189549336-e6e99c3679fe?w=800&q=80'
|
|
72
|
+
],
|
|
73
|
+
lifestyle: [
|
|
74
|
+
'https://images.unsplash.com/photo-1516321497487-e288fb19713f?w=800&q=80',
|
|
75
|
+
'https://images.unsplash.com/photo-1483985988355-763728e1935b?w=800&q=80',
|
|
76
|
+
'https://images.unsplash.com/photo-1556742049-0cfed4f6a45d?w=800&q=80',
|
|
77
|
+
'https://images.unsplash.com/photo-1472851294608-062f824d29cc?w=800&q=80',
|
|
78
|
+
'https://images.unsplash.com/photo-1525328437458-0c4d4db7cab4?w=800&q=80'
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
team: [
|
|
82
|
+
'https://images.unsplash.com/photo-1522071820081-009f0129c71c?w=400&q=80',
|
|
83
|
+
'https://images.unsplash.com/photo-1560250097-0b93528c311a?w=400&q=80',
|
|
84
|
+
'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=400&q=80',
|
|
85
|
+
'https://images.unsplash.com/photo-1519085360753-af0119f7cbe7?w=400&q=80',
|
|
86
|
+
'https://images.unsplash.com/photo-1580489944761-15a19d654956?w=400&q=80',
|
|
87
|
+
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&q=80',
|
|
88
|
+
'https://images.unsplash.com/photo-1494790108755-2616b612b786?w=400&q=80',
|
|
89
|
+
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&q=80'
|
|
90
|
+
],
|
|
91
|
+
testimonials: [
|
|
92
|
+
'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=200&q=80',
|
|
93
|
+
'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=200&q=80',
|
|
94
|
+
'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=200&q=80',
|
|
95
|
+
'https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=200&q=80',
|
|
96
|
+
'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=200&q=80',
|
|
97
|
+
'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200&q=80'
|
|
98
|
+
],
|
|
99
|
+
logos: [
|
|
100
|
+
'https://images.unsplash.com/photo-1611162617474-5b21e879e113?w=200&q=80',
|
|
101
|
+
'https://images.unsplash.com/photo-1611162616305-c69b3fa7fbe0?w=200&q=80',
|
|
102
|
+
'https://images.unsplash.com/photo-1611162618071-b39a2ec055fb?w=200&q=80'
|
|
103
|
+
],
|
|
104
|
+
icons: {
|
|
105
|
+
general: [
|
|
106
|
+
'https://images.unsplash.com/photo-1611162616475-46b635cb6868?w=100&q=80'
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
backgrounds: {
|
|
110
|
+
gradient: [
|
|
111
|
+
'https://images.unsplash.com/photo-1557683316-973673baf926?w=1920&q=80',
|
|
112
|
+
'https://images.unsplash.com/photo-1557682250-33bd709cbe85?w=1920&q=80',
|
|
113
|
+
'https://images.unsplash.com/photo-1557682224-5b8590cd9ec5?w=1920&q=80',
|
|
114
|
+
'https://images.unsplash.com/photo-1557682260-96773eb01377?w=1920&q=80'
|
|
115
|
+
],
|
|
116
|
+
pattern: [
|
|
117
|
+
'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=1920&q=80',
|
|
118
|
+
'https://images.unsplash.com/photo-1553356084-58ef4a67b2a7?w=1920&q=80'
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
// Unsplash API configuration
|
|
124
|
+
const UNSPLASH_API_URL = 'https://api.unsplash.com';
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* ImageService - AI-powered image generation and integration
|
|
128
|
+
*/
|
|
129
|
+
export class ImageService {
|
|
130
|
+
constructor(options = {}) {
|
|
131
|
+
this.accessKey = options.accessKey || process.env.UNSPLASH_ACCESS_KEY;
|
|
132
|
+
this.downloadPath = options.downloadPath || './public/images';
|
|
133
|
+
this.verbose = options.verbose || false;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Search Unsplash for images
|
|
138
|
+
*/
|
|
139
|
+
async searchImages(query, options = {}) {
|
|
140
|
+
const {
|
|
141
|
+
count = 5,
|
|
142
|
+
orientation = 'landscape', // landscape, portrait, squarish
|
|
143
|
+
size = 'regular' // raw, full, regular, small, thumb
|
|
144
|
+
} = options;
|
|
145
|
+
|
|
146
|
+
// If no API key, use curated fallback
|
|
147
|
+
if (!this.accessKey) {
|
|
148
|
+
return this.getCuratedImages(query, count);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const params = new URLSearchParams({
|
|
153
|
+
query,
|
|
154
|
+
per_page: count,
|
|
155
|
+
orientation
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const response = await this.fetchWithTimeout(
|
|
159
|
+
`${UNSPLASH_API_URL}/search/photos?${params}`,
|
|
160
|
+
{
|
|
161
|
+
headers: {
|
|
162
|
+
'Authorization': `Client-ID ${this.accessKey}`
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
if (!response.ok) {
|
|
168
|
+
throw new Error(`Unsplash API error: ${response.status}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const data = await response.json();
|
|
172
|
+
|
|
173
|
+
return data.results.map(img => ({
|
|
174
|
+
id: img.id,
|
|
175
|
+
url: img.urls[size],
|
|
176
|
+
downloadUrl: img.links.download_location,
|
|
177
|
+
alt: img.alt_description || query,
|
|
178
|
+
photographer: img.user.name,
|
|
179
|
+
photographerUrl: img.user.links.html,
|
|
180
|
+
width: img.width,
|
|
181
|
+
height: img.height
|
|
182
|
+
}));
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (this.verbose) {
|
|
185
|
+
console.log(chalk.yellow(` Unsplash API failed, using curated images`));
|
|
186
|
+
}
|
|
187
|
+
return this.getCuratedImages(query, count);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get curated images based on query (fallback when no API key)
|
|
193
|
+
*/
|
|
194
|
+
getCuratedImages(query, count = 5) {
|
|
195
|
+
const queryLower = query.toLowerCase();
|
|
196
|
+
let images = [];
|
|
197
|
+
|
|
198
|
+
// Match query to curated collections
|
|
199
|
+
if (queryLower.includes('hero') || queryLower.includes('banner')) {
|
|
200
|
+
if (queryLower.includes('tech')) {
|
|
201
|
+
images = CURATED_COLLECTIONS.hero.tech;
|
|
202
|
+
} else if (queryLower.includes('business') || queryLower.includes('corporate')) {
|
|
203
|
+
images = CURATED_COLLECTIONS.hero.business;
|
|
204
|
+
} else if (queryLower.includes('creative') || queryLower.includes('design')) {
|
|
205
|
+
images = CURATED_COLLECTIONS.hero.creative;
|
|
206
|
+
} else if (queryLower.includes('nature') || queryLower.includes('outdoor')) {
|
|
207
|
+
images = CURATED_COLLECTIONS.hero.nature;
|
|
208
|
+
} else {
|
|
209
|
+
images = CURATED_COLLECTIONS.hero.abstract;
|
|
210
|
+
}
|
|
211
|
+
} else if (queryLower.includes('product')) {
|
|
212
|
+
if (queryLower.includes('tech') || queryLower.includes('gadget')) {
|
|
213
|
+
images = CURATED_COLLECTIONS.products.tech;
|
|
214
|
+
} else if (queryLower.includes('fashion') || queryLower.includes('clothing')) {
|
|
215
|
+
images = CURATED_COLLECTIONS.products.fashion;
|
|
216
|
+
} else if (queryLower.includes('food')) {
|
|
217
|
+
images = CURATED_COLLECTIONS.products.food;
|
|
218
|
+
} else {
|
|
219
|
+
images = CURATED_COLLECTIONS.products.lifestyle;
|
|
220
|
+
}
|
|
221
|
+
} else if (queryLower.includes('team') || queryLower.includes('person') || queryLower.includes('people')) {
|
|
222
|
+
images = CURATED_COLLECTIONS.team;
|
|
223
|
+
} else if (queryLower.includes('testimonial') || queryLower.includes('avatar')) {
|
|
224
|
+
images = CURATED_COLLECTIONS.testimonials;
|
|
225
|
+
} else if (queryLower.includes('logo') || queryLower.includes('brand')) {
|
|
226
|
+
images = CURATED_COLLECTIONS.logos;
|
|
227
|
+
} else if (queryLower.includes('background') || queryLower.includes('bg')) {
|
|
228
|
+
if (queryLower.includes('gradient')) {
|
|
229
|
+
images = CURATED_COLLECTIONS.backgrounds.gradient;
|
|
230
|
+
} else {
|
|
231
|
+
images = CURATED_COLLECTIONS.backgrounds.pattern;
|
|
232
|
+
}
|
|
233
|
+
} else {
|
|
234
|
+
// Default to abstract hero images
|
|
235
|
+
images = CURATED_COLLECTIONS.hero.abstract;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Shuffle and take requested count
|
|
239
|
+
const shuffled = [...images].sort(() => Math.random() - 0.5);
|
|
240
|
+
return shuffled.slice(0, count).map((url, index) => ({
|
|
241
|
+
id: `curated-${index}`,
|
|
242
|
+
url,
|
|
243
|
+
downloadUrl: url,
|
|
244
|
+
alt: query,
|
|
245
|
+
photographer: 'Unsplash',
|
|
246
|
+
photographerUrl: 'https://unsplash.com',
|
|
247
|
+
width: 1920,
|
|
248
|
+
height: 1080
|
|
249
|
+
}));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Download image to local path
|
|
254
|
+
*/
|
|
255
|
+
async downloadImage(imageUrl, filename, options = {}) {
|
|
256
|
+
const { directory = this.downloadPath } = options;
|
|
257
|
+
|
|
258
|
+
// Ensure directory exists
|
|
259
|
+
await fs.mkdir(directory, { recursive: true });
|
|
260
|
+
|
|
261
|
+
const filePath = path.join(directory, filename);
|
|
262
|
+
|
|
263
|
+
return new Promise((resolve, reject) => {
|
|
264
|
+
const file = require('fs').createWriteStream(filePath);
|
|
265
|
+
|
|
266
|
+
https.get(imageUrl, (response) => {
|
|
267
|
+
// Handle redirects
|
|
268
|
+
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
269
|
+
const redirectUrl = response.headers.location;
|
|
270
|
+
https.get(redirectUrl, (redirectResponse) => {
|
|
271
|
+
redirectResponse.pipe(file);
|
|
272
|
+
file.on('finish', () => {
|
|
273
|
+
file.close();
|
|
274
|
+
resolve(filePath);
|
|
275
|
+
});
|
|
276
|
+
}).on('error', reject);
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
response.pipe(file);
|
|
281
|
+
file.on('finish', () => {
|
|
282
|
+
file.close();
|
|
283
|
+
resolve(filePath);
|
|
284
|
+
});
|
|
285
|
+
}).on('error', (err) => {
|
|
286
|
+
fs.unlink(filePath).catch(() => {});
|
|
287
|
+
reject(err);
|
|
288
|
+
});
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Generate images for a project
|
|
294
|
+
*/
|
|
295
|
+
async generateProjectImages(projectPath, projectType, options = {}) {
|
|
296
|
+
const {
|
|
297
|
+
hero = true,
|
|
298
|
+
products = 0,
|
|
299
|
+
team = 0,
|
|
300
|
+
testimonials = 0,
|
|
301
|
+
theme = 'tech'
|
|
302
|
+
} = options;
|
|
303
|
+
|
|
304
|
+
const imagesDir = path.join(projectPath, 'public', 'images');
|
|
305
|
+
await fs.mkdir(imagesDir, { recursive: true });
|
|
306
|
+
|
|
307
|
+
const results = {
|
|
308
|
+
downloaded: [],
|
|
309
|
+
failed: []
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
console.log(chalk.cyan('\n 📸 Generating project images...\n'));
|
|
313
|
+
|
|
314
|
+
// Hero image
|
|
315
|
+
if (hero) {
|
|
316
|
+
try {
|
|
317
|
+
const heroImages = await this.searchImages(`hero ${theme}`, { count: 1 });
|
|
318
|
+
if (heroImages.length > 0) {
|
|
319
|
+
const filename = 'hero.jpg';
|
|
320
|
+
await this.downloadImage(heroImages[0].url, filename, { directory: imagesDir });
|
|
321
|
+
results.downloaded.push({ type: 'hero', filename, url: heroImages[0].url });
|
|
322
|
+
console.log(chalk.green(` ✓ Hero image downloaded`));
|
|
323
|
+
}
|
|
324
|
+
} catch (error) {
|
|
325
|
+
results.failed.push({ type: 'hero', error: error.message });
|
|
326
|
+
console.log(chalk.yellow(` ⚠ Hero image failed: ${error.message}`));
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Product images
|
|
331
|
+
if (products > 0) {
|
|
332
|
+
try {
|
|
333
|
+
const productImages = await this.searchImages(`product ${theme}`, { count: products });
|
|
334
|
+
for (let i = 0; i < productImages.length; i++) {
|
|
335
|
+
const filename = `product-${i + 1}.jpg`;
|
|
336
|
+
await this.downloadImage(productImages[i].url, filename, { directory: imagesDir });
|
|
337
|
+
results.downloaded.push({ type: 'product', filename, url: productImages[i].url });
|
|
338
|
+
}
|
|
339
|
+
console.log(chalk.green(` ✓ ${productImages.length} product images downloaded`));
|
|
340
|
+
} catch (error) {
|
|
341
|
+
results.failed.push({ type: 'products', error: error.message });
|
|
342
|
+
console.log(chalk.yellow(` ⚠ Product images failed: ${error.message}`));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Team photos
|
|
347
|
+
if (team > 0) {
|
|
348
|
+
try {
|
|
349
|
+
const teamImages = this.getCuratedImages('team', team);
|
|
350
|
+
for (let i = 0; i < teamImages.length; i++) {
|
|
351
|
+
const filename = `team-${i + 1}.jpg`;
|
|
352
|
+
await this.downloadImage(teamImages[i].url, filename, { directory: imagesDir });
|
|
353
|
+
results.downloaded.push({ type: 'team', filename, url: teamImages[i].url });
|
|
354
|
+
}
|
|
355
|
+
console.log(chalk.green(` ✓ ${teamImages.length} team photos downloaded`));
|
|
356
|
+
} catch (error) {
|
|
357
|
+
results.failed.push({ type: 'team', error: error.message });
|
|
358
|
+
console.log(chalk.yellow(` ⚠ Team photos failed: ${error.message}`));
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Testimonial avatars
|
|
363
|
+
if (testimonials > 0) {
|
|
364
|
+
try {
|
|
365
|
+
const testimonialImages = this.getCuratedImages('testimonial', testimonials);
|
|
366
|
+
for (let i = 0; i < testimonialImages.length; i++) {
|
|
367
|
+
const filename = `testimonial-${i + 1}.jpg`;
|
|
368
|
+
await this.downloadImage(testimonialImages[i].url, filename, { directory: imagesDir });
|
|
369
|
+
results.downloaded.push({ type: 'testimonial', filename, url: testimonialImages[i].url });
|
|
370
|
+
}
|
|
371
|
+
console.log(chalk.green(` ✓ ${testimonialImages.length} testimonial avatars downloaded`));
|
|
372
|
+
} catch (error) {
|
|
373
|
+
results.failed.push({ type: 'testimonials', error: error.message });
|
|
374
|
+
console.log(chalk.yellow(` ⚠ Testimonial avatars failed: ${error.message}`));
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
console.log(chalk.gray(`\n Total: ${results.downloaded.length} images downloaded to ${imagesDir}\n`));
|
|
379
|
+
|
|
380
|
+
return results;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Replace placeholder images in project files
|
|
385
|
+
*/
|
|
386
|
+
async replacePlaceholders(projectPath, options = {}) {
|
|
387
|
+
const { extensions = ['.js', '.jsx', '.tsx', '.html', '.vue', '.svelte'] } = options;
|
|
388
|
+
|
|
389
|
+
const placeholderPatterns = [
|
|
390
|
+
/https?:\/\/via\.placeholder\.com\/\d+x?\d*/g,
|
|
391
|
+
/https?:\/\/placehold\.co\/\d+x?\d*/g,
|
|
392
|
+
/https?:\/\/placekitten\.com\/\d+\/?\d*/g,
|
|
393
|
+
/https?:\/\/picsum\.photos\/\d+\/?\d*/g,
|
|
394
|
+
/\/placeholder\.(jpg|png|svg)/g
|
|
395
|
+
];
|
|
396
|
+
|
|
397
|
+
let replacedCount = 0;
|
|
398
|
+
|
|
399
|
+
const processFile = async (filePath) => {
|
|
400
|
+
try {
|
|
401
|
+
let content = await fs.readFile(filePath, 'utf-8');
|
|
402
|
+
let modified = false;
|
|
403
|
+
|
|
404
|
+
for (const pattern of placeholderPatterns) {
|
|
405
|
+
const matches = content.match(pattern);
|
|
406
|
+
if (matches) {
|
|
407
|
+
for (const match of matches) {
|
|
408
|
+
// Determine replacement based on context
|
|
409
|
+
const images = this.getCuratedImages('abstract', 1);
|
|
410
|
+
if (images.length > 0) {
|
|
411
|
+
content = content.replace(match, images[0].url);
|
|
412
|
+
modified = true;
|
|
413
|
+
replacedCount++;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
if (modified) {
|
|
420
|
+
await fs.writeFile(filePath, content);
|
|
421
|
+
if (this.verbose) {
|
|
422
|
+
console.log(chalk.gray(` Updated: ${path.relative(projectPath, filePath)}`));
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
} catch (error) {
|
|
426
|
+
// Skip files that can't be read
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const walkDir = async (dir) => {
|
|
431
|
+
try {
|
|
432
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
433
|
+
|
|
434
|
+
for (const entry of entries) {
|
|
435
|
+
const fullPath = path.join(dir, entry.name);
|
|
436
|
+
|
|
437
|
+
// Skip node_modules and hidden directories
|
|
438
|
+
if (entry.isDirectory()) {
|
|
439
|
+
if (entry.name !== 'node_modules' && !entry.name.startsWith('.')) {
|
|
440
|
+
await walkDir(fullPath);
|
|
441
|
+
}
|
|
442
|
+
} else if (entry.isFile()) {
|
|
443
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
444
|
+
if (extensions.includes(ext)) {
|
|
445
|
+
await processFile(fullPath);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
} catch (error) {
|
|
450
|
+
// Skip directories that can't be read
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
await walkDir(projectPath);
|
|
455
|
+
|
|
456
|
+
return { replacedCount };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Fetch with timeout helper
|
|
461
|
+
*/
|
|
462
|
+
async fetchWithTimeout(url, options = {}, timeout = 10000) {
|
|
463
|
+
const controller = new AbortController();
|
|
464
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
465
|
+
|
|
466
|
+
try {
|
|
467
|
+
const response = await fetch(url, {
|
|
468
|
+
...options,
|
|
469
|
+
signal: controller.signal
|
|
470
|
+
});
|
|
471
|
+
clearTimeout(timeoutId);
|
|
472
|
+
return response;
|
|
473
|
+
} catch (error) {
|
|
474
|
+
clearTimeout(timeoutId);
|
|
475
|
+
throw error;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Create ImageService instance
|
|
482
|
+
*/
|
|
483
|
+
export function createImageService(options = {}) {
|
|
484
|
+
return new ImageService(options);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Quick image search helper
|
|
489
|
+
*/
|
|
490
|
+
export async function searchImages(query, options = {}) {
|
|
491
|
+
const service = createImageService(options);
|
|
492
|
+
return service.searchImages(query, options);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Generate images for project helper
|
|
497
|
+
*/
|
|
498
|
+
export async function generateImages(projectPath, projectType, options = {}) {
|
|
499
|
+
const service = createImageService(options);
|
|
500
|
+
return service.generateProjectImages(projectPath, projectType, options);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/**
|
|
504
|
+
* Get curated collection
|
|
505
|
+
*/
|
|
506
|
+
export function getCuratedCollection(type, subtype = null) {
|
|
507
|
+
if (subtype && CURATED_COLLECTIONS[type]?.[subtype]) {
|
|
508
|
+
return CURATED_COLLECTIONS[type][subtype];
|
|
509
|
+
}
|
|
510
|
+
return CURATED_COLLECTIONS[type] || [];
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export { CURATED_COLLECTIONS };
|