myaidev-method 0.2.12 → 0.2.15

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.
Files changed (28) hide show
  1. package/.claude/CLAUDE.md +46 -0
  2. package/.claude/agents/content-production-coordinator.md +111 -0
  3. package/.claude/agents/proprietary-content-verifier.md +96 -0
  4. package/.claude/agents/visual-content-generator.md +520 -0
  5. package/.claude/commands/myai-coordinate-content.md +136 -0
  6. package/.claude/settings.local.json +3 -2
  7. package/.env.example +33 -0
  8. package/CHANGELOG.md +64 -0
  9. package/CONTENT_CREATION_GUIDE.md +3399 -0
  10. package/DEVELOPER_USE_CASES.md +2085 -0
  11. package/README.md +209 -2
  12. package/VISUAL_GENERATION_FILE_ORGANIZATION.md +105 -0
  13. package/bin/cli.js +46 -0
  14. package/package.json +17 -2
  15. package/src/lib/asset-management.js +532 -0
  16. package/src/lib/visual-config-utils.js +424 -0
  17. package/src/lib/visual-generation-utils.js +668 -0
  18. package/src/scripts/configure-visual-apis.js +413 -0
  19. package/src/scripts/generate-visual-cli.js +279 -0
  20. package/src/templates/claude/agents/content-production-coordinator.md +111 -0
  21. package/src/templates/claude/agents/content-writer.md +209 -4
  22. package/src/templates/claude/agents/proprietary-content-verifier.md +96 -0
  23. package/src/templates/claude/agents/visual-content-generator.md +520 -0
  24. package/src/templates/claude/commands/myai-content-writer.md +33 -8
  25. package/src/templates/claude/commands/myai-coordinate-content.md +136 -0
  26. package/src/templates/claude/commands/myai-generate-visual.md +318 -0
  27. package/src/templates/codex/commands/myai-generate-visual.md +307 -0
  28. package/src/templates/gemini/commands/myai-generate-visual.md +200 -0
@@ -0,0 +1,668 @@
1
+ /**
2
+ * Visual Content Generation Utilities
3
+ *
4
+ * Provides image and video generation capabilities using:
5
+ * - Google Gemini 2.5 Flash Image ("Nano Banana")
6
+ * - Google Imagen 3
7
+ * - OpenAI GPT-Image-1 (GPT-4o Image Generation)
8
+ * - Google Veo 2 (video)
9
+ *
10
+ * Platform support: Claude Code, Gemini CLI, Codex CLI
11
+ *
12
+ * @module visual-generation-utils
13
+ */
14
+
15
+ import fetch from 'node-fetch';
16
+ import fs from 'fs-extra';
17
+ import path from 'path';
18
+ import dotenv from 'dotenv';
19
+ import { GoogleAuth } from 'google-auth-library';
20
+
21
+ dotenv.config();
22
+
23
+ // API Configuration
24
+ const GOOGLE_API_BASE = 'https://generativelanguage.googleapis.com/v1beta';
25
+ const OPENAI_API_BASE = 'https://api.openai.com/v1';
26
+
27
+ /**
28
+ * Get OAuth2 access token for Vertex AI
29
+ * Uses Google Application Default Credentials (ADC)
30
+ *
31
+ * @returns {Promise<string>} OAuth2 access token
32
+ * @throws {Error} If authentication fails
33
+ */
34
+ async function getVertexAIToken() {
35
+ try {
36
+ const auth = new GoogleAuth({
37
+ scopes: ['https://www.googleapis.com/auth/cloud-platform']
38
+ });
39
+
40
+ const client = await auth.getClient();
41
+ const tokenResponse = await client.getAccessToken();
42
+
43
+ if (!tokenResponse.token) {
44
+ throw new Error('Failed to obtain access token');
45
+ }
46
+
47
+ return tokenResponse.token;
48
+ } catch (error) {
49
+ throw new Error(`Vertex AI authentication failed: ${error.message}`);
50
+ }
51
+ }
52
+
53
+ // Pricing (USD per image/video) - GPT-Image-1 pricing
54
+ const PRICING = {
55
+ gemini: 0.02, // Gemini 2.5 Flash Image
56
+ imagen: 0.03, // Imagen 3
57
+ dalle_low: 0.02, // GPT-Image-1 low quality
58
+ dalle_medium: 0.07, // GPT-Image-1 medium quality
59
+ dalle_standard: 0.07, // GPT-Image-1 medium quality (alias for standard)
60
+ dalle_high: 0.19, // GPT-Image-1 high quality
61
+ dalle_hd: 0.19, // GPT-Image-1 high quality (alias for hd)
62
+ veo: 0.10 // Veo 2 (estimated per video)
63
+ };
64
+
65
+ /**
66
+ * Validate that required API keys are configured
67
+ *
68
+ * @returns {Object} Validation results
69
+ * @returns {boolean} hasGoogle - Google API key is configured
70
+ * @returns {boolean} hasOpenAI - OpenAI API key is configured
71
+ * @returns {boolean} hasAny - At least one API key is configured
72
+ * @returns {Array<string>} availableServices - List of available services
73
+ */
74
+ export function validateAPIKeys() {
75
+ const googleKey = process.env.GOOGLE_API_KEY;
76
+ const openaiKey = process.env.OPENAI_API_KEY;
77
+
78
+ const hasGoogle = !!(googleKey && googleKey.length > 20);
79
+ const hasOpenAI = !!(openaiKey && openaiKey.length > 20);
80
+
81
+ const availableServices = [];
82
+ if (hasGoogle) {
83
+ availableServices.push('gemini', 'imagen', 'veo');
84
+ }
85
+ if (hasOpenAI) {
86
+ availableServices.push('dalle');
87
+ }
88
+
89
+ return {
90
+ hasGoogle,
91
+ hasOpenAI,
92
+ hasAny: hasGoogle || hasOpenAI,
93
+ availableServices
94
+ };
95
+ }
96
+
97
+ /**
98
+ * Estimate cost for image/video generation
99
+ *
100
+ * @param {string} service - Service name (gemini, imagen, dalle, veo)
101
+ * @param {Object} options - Generation options
102
+ * @param {string} options.quality - Quality level (standard, hd)
103
+ * @param {string} options.size - Image size
104
+ * @returns {number} Estimated cost in USD
105
+ */
106
+ export function estimateCost(service, options = {}) {
107
+ const { quality = 'high', size = '1024x1024' } = options;
108
+
109
+ switch (service) {
110
+ case 'gemini':
111
+ return PRICING.gemini;
112
+
113
+ case 'imagen':
114
+ return PRICING.imagen;
115
+
116
+ case 'dalle':
117
+ // GPT-Image-1 quality-based pricing
118
+ if (quality === 'low') {
119
+ return PRICING.dalle_low;
120
+ } else if (quality === 'medium' || quality === 'standard') {
121
+ return PRICING.dalle_medium;
122
+ } else if (quality === 'high' || quality === 'hd') {
123
+ return PRICING.dalle_high;
124
+ }
125
+ return PRICING.dalle_high; // default to high quality
126
+
127
+ case 'veo':
128
+ return PRICING.veo;
129
+
130
+ default:
131
+ return 0;
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Select best available service based on preferences
137
+ *
138
+ * @param {string} preferred - Preferred service name
139
+ * @returns {string} Selected service name
140
+ * @throws {Error} If no API keys are configured
141
+ */
142
+ export function selectBestService(preferred = 'gemini') {
143
+ const { availableServices, hasAny } = validateAPIKeys();
144
+
145
+ if (!hasAny) {
146
+ throw new Error('No API keys configured. Run /myai-configure visual to set up image generation.');
147
+ }
148
+
149
+ // Return preferred service if available
150
+ if (availableServices.includes(preferred)) {
151
+ return preferred;
152
+ }
153
+
154
+ // Fallback to first available service
155
+ return availableServices[0];
156
+ }
157
+
158
+ /**
159
+ * Generate image using Google Gemini 2.5 Flash Image ("Nano Banana")
160
+ * Fast and cost-effective image generation
161
+ *
162
+ * @param {string} prompt - Image description
163
+ * @param {Object} options - Generation options
164
+ * @param {number} options.aspectRatio - Aspect ratio (1 for square, 16/9 for wide)
165
+ * @param {number} options.maxRetries - Maximum retry attempts
166
+ * @returns {Promise<Object>} Generated image data
167
+ */
168
+ export async function generateImageGemini(prompt, options = {}) {
169
+ const {
170
+ aspectRatio = 1,
171
+ maxRetries = 3
172
+ } = options;
173
+
174
+ const apiKey = process.env.GOOGLE_API_KEY;
175
+ if (!apiKey) {
176
+ throw new Error('GOOGLE_API_KEY not configured');
177
+ }
178
+
179
+ const endpoint = `${GOOGLE_API_BASE}/models/gemini-2.0-flash-exp:generateContent`;
180
+
181
+ const requestBody = {
182
+ contents: [{
183
+ parts: [{
184
+ text: `Generate an image: ${prompt}\n\nAspect ratio: ${aspectRatio === 16/9 ? '16:9' : '1:1'}\nStyle: Professional, high quality, suitable for article content`
185
+ }]
186
+ }],
187
+ generationConfig: {
188
+ temperature: 0.4,
189
+ topP: 0.95,
190
+ topK: 40
191
+ }
192
+ };
193
+
194
+ let lastError;
195
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
196
+ try {
197
+ const response = await fetch(`${endpoint}?key=${apiKey}`, {
198
+ method: 'POST',
199
+ headers: {
200
+ 'Content-Type': 'application/json'
201
+ },
202
+ body: JSON.stringify(requestBody)
203
+ });
204
+
205
+ if (!response.ok) {
206
+ const errorText = await response.text();
207
+ throw new Error(`Gemini API error: ${response.status} - ${errorText}`);
208
+ }
209
+
210
+ const data = await response.json();
211
+
212
+ // Extract image data from response
213
+ if (data.candidates && data.candidates[0]) {
214
+ const candidate = data.candidates[0];
215
+
216
+ // Gemini returns inline data or content references
217
+ if (candidate.content && candidate.content.parts) {
218
+ for (const part of candidate.content.parts) {
219
+ if (part.inlineData && part.inlineData.data) {
220
+ return {
221
+ data: part.inlineData.data,
222
+ mimeType: part.inlineData.mimeType || 'image/png',
223
+ service: 'gemini',
224
+ cost: PRICING.gemini
225
+ };
226
+ }
227
+ }
228
+ }
229
+ }
230
+
231
+ throw new Error('No image data in Gemini response');
232
+
233
+ } catch (error) {
234
+ lastError = error;
235
+
236
+ if (attempt < maxRetries) {
237
+ const backoff = Math.pow(2, attempt) * 1000;
238
+ console.log(`⚠️ Gemini attempt ${attempt} failed. Retrying in ${backoff/1000}s...`);
239
+ await sleep(backoff);
240
+ }
241
+ }
242
+ }
243
+
244
+ throw lastError;
245
+ }
246
+
247
+ /**
248
+ * Generate image using Google Imagen 4 (via Vertex AI)
249
+ * Premium quality image generation
250
+ *
251
+ * Requires Vertex AI setup:
252
+ * - GOOGLE_CLOUD_PROJECT_ID environment variable
253
+ * - GOOGLE_CLOUD_LOCATION environment variable (default: us-central1)
254
+ * - GOOGLE_APPLICATION_CREDENTIALS pointing to service account key JSON
255
+ *
256
+ * @param {string} prompt - Image description
257
+ * @param {Object} options - Generation options
258
+ * @param {string} options.size - Image size (256x256, 1024x1024)
259
+ * @param {number} options.maxRetries - Maximum retry attempts
260
+ * @returns {Promise<Object>} Generated image data
261
+ */
262
+ export async function generateImageImagen(prompt, options = {}) {
263
+ const {
264
+ size = '1024x1024',
265
+ maxRetries = 3
266
+ } = options;
267
+
268
+ // Vertex AI configuration
269
+ const projectId = process.env.GOOGLE_CLOUD_PROJECT_ID;
270
+ const location = process.env.GOOGLE_CLOUD_LOCATION || 'us-central1';
271
+
272
+ if (!projectId) {
273
+ throw new Error('GOOGLE_CLOUD_PROJECT_ID not configured. Set up Vertex AI credentials.');
274
+ }
275
+
276
+ // Build Vertex AI endpoint
277
+ const endpoint = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/imagen-4.0-generate-001:predict`;
278
+
279
+ const requestBody = {
280
+ instances: [{
281
+ prompt: prompt
282
+ }],
283
+ parameters: {
284
+ sampleCount: 1,
285
+ aspectRatio: size === '1024x1024' ? '1:1' : (size.includes('1792') ? '16:9' : '1:1'),
286
+ safetyFilterLevel: 'block_some',
287
+ personGeneration: 'allow_adult'
288
+ }
289
+ };
290
+
291
+ let lastError;
292
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
293
+ try {
294
+ // Get OAuth2 access token
295
+ const token = await getVertexAIToken();
296
+
297
+ const response = await fetch(endpoint, {
298
+ method: 'POST',
299
+ headers: {
300
+ 'Authorization': `Bearer ${token}`,
301
+ 'Content-Type': 'application/json'
302
+ },
303
+ body: JSON.stringify(requestBody)
304
+ });
305
+
306
+ if (!response.ok) {
307
+ const errorText = await response.text();
308
+ throw new Error(`Imagen API error: ${response.status} - ${errorText}`);
309
+ }
310
+
311
+ const data = await response.json();
312
+
313
+ if (data.predictions && data.predictions[0]) {
314
+ const prediction = data.predictions[0];
315
+
316
+ // Imagen 4 returns base64-encoded image in bytesBase64Encoded
317
+ if (prediction.bytesBase64Encoded) {
318
+ return {
319
+ data: prediction.bytesBase64Encoded,
320
+ mimeType: prediction.mimeType || 'image/png',
321
+ service: 'imagen',
322
+ cost: PRICING.imagen
323
+ };
324
+ }
325
+ }
326
+
327
+ throw new Error('No image data in Imagen response');
328
+
329
+ } catch (error) {
330
+ lastError = error;
331
+
332
+ if (attempt < maxRetries) {
333
+ const backoff = Math.pow(2, attempt) * 1000;
334
+ console.log(`⚠️ Imagen attempt ${attempt} failed. Retrying in ${backoff/1000}s...`);
335
+ await sleep(backoff);
336
+ }
337
+ }
338
+ }
339
+
340
+ throw lastError;
341
+ }
342
+
343
+ /**
344
+ * Generate image using OpenAI DALL-E 3
345
+ * Creative, high-quality image generation
346
+ *
347
+ * @param {string} prompt - Image description
348
+ * @param {Object} options - Generation options
349
+ * @param {string} options.size - Image size (1024x1024, 1024x1792, 1792x1024)
350
+ * @param {string} options.quality - Quality level (standard, hd)
351
+ * @param {string} options.style - Style (vivid, natural)
352
+ * @param {number} options.maxRetries - Maximum retry attempts
353
+ * @returns {Promise<Object>} Generated image data with URL
354
+ */
355
+ export async function generateImageDALLE(prompt, options = {}) {
356
+ const {
357
+ size = '1024x1024',
358
+ quality = 'high', // low, medium, or high
359
+ maxRetries = 3
360
+ } = options;
361
+
362
+ const apiKey = process.env.OPENAI_API_KEY;
363
+ if (!apiKey) {
364
+ throw new Error('OPENAI_API_KEY not configured');
365
+ }
366
+
367
+ const endpoint = `${OPENAI_API_BASE}/images/generations`;
368
+
369
+ // Try GPT-Image-1 first, fall back to DALL-E 3 if not available
370
+ const model = 'dall-e-3'; // Will use gpt-image-1 once org is verified
371
+
372
+ const requestBody = {
373
+ model: model,
374
+ prompt: prompt,
375
+ n: 1,
376
+ size: size,
377
+ ...(model === 'gpt-image-1' ? { quality } : { quality: quality === 'low' ? 'standard' : 'hd' })
378
+ };
379
+
380
+ let lastError;
381
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
382
+ try {
383
+ const response = await fetch(endpoint, {
384
+ method: 'POST',
385
+ headers: {
386
+ 'Content-Type': 'application/json',
387
+ 'Authorization': `Bearer ${apiKey}`
388
+ },
389
+ body: JSON.stringify(requestBody)
390
+ });
391
+
392
+ if (!response.ok) {
393
+ const errorData = await response.json();
394
+ throw new Error(`DALL-E API error: ${response.status} - ${errorData.error?.message || 'Unknown error'}`);
395
+ }
396
+
397
+ const data = await response.json();
398
+
399
+ if (data.data && data.data[0]) {
400
+ const image = data.data[0];
401
+
402
+ return {
403
+ url: image.url,
404
+ revisedPrompt: image.revised_prompt, // DALL-E often revises prompts
405
+ mimeType: 'image/png',
406
+ service: 'dalle',
407
+ cost: estimateCost('dalle', { quality, size })
408
+ };
409
+ }
410
+
411
+ throw new Error('No image data in DALL-E response');
412
+
413
+ } catch (error) {
414
+ lastError = error;
415
+
416
+ if (error.message.includes('rate_limit')) {
417
+ if (attempt < maxRetries) {
418
+ const backoff = Math.pow(2, attempt) * 1000;
419
+ console.log(`⚠️ DALL-E rate limited. Retrying in ${backoff/1000}s...`);
420
+ await sleep(backoff);
421
+ }
422
+ } else {
423
+ throw error; // Don't retry on other errors
424
+ }
425
+ }
426
+ }
427
+
428
+ throw lastError;
429
+ }
430
+
431
+ /**
432
+ * Generate video using Google Veo 2
433
+ * AI video generation from text prompts
434
+ *
435
+ * @param {string} prompt - Video description
436
+ * @param {Object} options - Generation options
437
+ * @param {number} options.duration - Video duration in seconds (max 8)
438
+ * @param {string} options.aspectRatio - Aspect ratio (16:9, 9:16, 1:1)
439
+ * @param {number} options.maxRetries - Maximum retry attempts
440
+ * @returns {Promise<Object>} Generated video data
441
+ */
442
+ export async function generateVideoVeo(prompt, options = {}) {
443
+ const {
444
+ duration = 5,
445
+ aspectRatio = '16:9',
446
+ maxRetries = 3
447
+ } = options;
448
+
449
+ const apiKey = process.env.GOOGLE_API_KEY;
450
+ if (!apiKey) {
451
+ throw new Error('GOOGLE_API_KEY not configured');
452
+ }
453
+
454
+ const endpoint = `${GOOGLE_API_BASE}/models/veo-2.0-generate-001:predict`;
455
+
456
+ const requestBody = {
457
+ instances: [{
458
+ prompt: prompt
459
+ }],
460
+ parameters: {
461
+ duration: Math.min(duration, 8), // Max 8 seconds
462
+ aspectRatio: aspectRatio,
463
+ quality: '720p'
464
+ }
465
+ };
466
+
467
+ let lastError;
468
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
469
+ try {
470
+ const response = await fetch(`${endpoint}?key=${apiKey}`, {
471
+ method: 'POST',
472
+ headers: {
473
+ 'Content-Type': 'application/json'
474
+ },
475
+ body: JSON.stringify(requestBody)
476
+ });
477
+
478
+ if (!response.ok) {
479
+ const errorText = await response.text();
480
+ throw new Error(`Veo API error: ${response.status} - ${errorText}`);
481
+ }
482
+
483
+ const data = await response.json();
484
+
485
+ if (data.predictions && data.predictions[0]) {
486
+ const prediction = data.predictions[0];
487
+
488
+ // Veo returns video data or URL
489
+ if (prediction.videoData || prediction.url) {
490
+ return {
491
+ data: prediction.videoData,
492
+ url: prediction.url,
493
+ mimeType: 'video/mp4',
494
+ service: 'veo',
495
+ cost: PRICING.veo,
496
+ duration: duration
497
+ };
498
+ }
499
+ }
500
+
501
+ throw new Error('No video data in Veo response');
502
+
503
+ } catch (error) {
504
+ lastError = error;
505
+
506
+ if (attempt < maxRetries) {
507
+ const backoff = Math.pow(2, attempt) * 2000; // Longer backoff for video
508
+ console.log(`⚠️ Veo attempt ${attempt} failed. Retrying in ${backoff/1000}s...`);
509
+ await sleep(backoff);
510
+ }
511
+ }
512
+ }
513
+
514
+ throw lastError;
515
+ }
516
+
517
+ /**
518
+ * Download image from URL and return buffer
519
+ *
520
+ * @param {string} url - Image URL
521
+ * @returns {Promise<Buffer>} Image buffer
522
+ */
523
+ export async function downloadImage(url) {
524
+ const response = await fetch(url);
525
+
526
+ if (!response.ok) {
527
+ throw new Error(`Failed to download image: ${response.status}`);
528
+ }
529
+
530
+ const arrayBuffer = await response.arrayBuffer();
531
+ return Buffer.from(arrayBuffer);
532
+ }
533
+
534
+ /**
535
+ * Generate image using auto-selected service
536
+ * Automatically selects best available service based on preferences
537
+ *
538
+ * @param {string} prompt - Image description
539
+ * @param {Object} options - Generation options
540
+ * @param {string} options.preferredService - Preferred service (gemini, imagen, dalle)
541
+ * @param {string} options.type - Image type for optimization (hero, illustration, diagram)
542
+ * @returns {Promise<Object>} Generated image data with buffer
543
+ */
544
+ export async function generateImage(prompt, options = {}) {
545
+ const { preferredService, type = 'general', ...serviceOptions } = options;
546
+
547
+ // Select service
548
+ const defaultService = process.env.VISUAL_DEFAULT_SERVICE || 'gemini';
549
+ const service = selectBestService(preferredService || defaultService);
550
+
551
+ console.log(`🎨 Generating ${type} image using ${service}...`);
552
+
553
+ // Enhance prompt based on image type
554
+ const enhancedPrompt = enhancePrompt(prompt, type);
555
+
556
+ // Generate based on service
557
+ let result;
558
+ switch (service) {
559
+ case 'gemini':
560
+ result = await generateImageGemini(enhancedPrompt, serviceOptions);
561
+ break;
562
+
563
+ case 'imagen':
564
+ result = await generateImageImagen(enhancedPrompt, serviceOptions);
565
+ break;
566
+
567
+ case 'dalle':
568
+ result = await generateImageDALLE(enhancedPrompt, serviceOptions);
569
+ break;
570
+
571
+ default:
572
+ throw new Error(`Unknown service: ${service}`);
573
+ }
574
+
575
+ // Convert to buffer
576
+ let buffer;
577
+ if (result.data) {
578
+ // Base64 encoded data
579
+ buffer = Buffer.from(result.data, 'base64');
580
+ } else if (result.url) {
581
+ // Download from URL
582
+ buffer = await downloadImage(result.url);
583
+ } else {
584
+ throw new Error('No image data or URL in response');
585
+ }
586
+
587
+ return {
588
+ ...result,
589
+ buffer,
590
+ prompt: enhancedPrompt,
591
+ originalPrompt: prompt
592
+ };
593
+ }
594
+
595
+ /**
596
+ * Enhance prompt based on image type
597
+ *
598
+ * @param {string} prompt - Original prompt
599
+ * @param {string} type - Image type (hero, illustration, diagram, screenshot)
600
+ * @returns {string} Enhanced prompt
601
+ */
602
+ function enhancePrompt(prompt, type) {
603
+ const enhancements = {
604
+ hero: 'Professional hero image, high quality, visually striking, suitable for article header:',
605
+ illustration: 'Clean illustration, professional style, clear and informative:',
606
+ diagram: 'Technical diagram, clear labels, professional design, easy to understand:',
607
+ screenshot: 'Professional screenshot, clean interface, high resolution:',
608
+ general: 'High quality image, professional style:'
609
+ };
610
+
611
+ const prefix = enhancements[type] || enhancements.general;
612
+ return `${prefix} ${prompt}`;
613
+ }
614
+
615
+ /**
616
+ * Sleep utility for retry backoff
617
+ *
618
+ * @param {number} ms - Milliseconds to sleep
619
+ * @returns {Promise<void>}
620
+ */
621
+ function sleep(ms) {
622
+ return new Promise(resolve => setTimeout(resolve, ms));
623
+ }
624
+
625
+ /**
626
+ * Get service information
627
+ *
628
+ * @param {string} service - Service name
629
+ * @returns {Object} Service information
630
+ */
631
+ export function getServiceInfo(service) {
632
+ const info = {
633
+ gemini: {
634
+ name: 'Gemini 2.5 Flash Image',
635
+ nickname: 'Nano Banana',
636
+ speed: 'Fast',
637
+ cost: '$0.02/image',
638
+ quality: 'Good',
639
+ bestFor: 'Quick hero images, high volume'
640
+ },
641
+ imagen: {
642
+ name: 'Imagen 3',
643
+ nickname: 'Premium Quality',
644
+ speed: 'Medium',
645
+ cost: '$0.03/image',
646
+ quality: 'Excellent',
647
+ bestFor: 'Premium hero images, photorealistic'
648
+ },
649
+ dalle: {
650
+ name: 'DALL-E 3',
651
+ nickname: 'Creative',
652
+ speed: 'Medium',
653
+ cost: '$0.04-0.12/image',
654
+ quality: 'Excellent',
655
+ bestFor: 'Creative illustrations, concept art'
656
+ },
657
+ veo: {
658
+ name: 'Veo 2',
659
+ nickname: 'Video Generation',
660
+ speed: 'Slow',
661
+ cost: '$0.10/video (estimated)',
662
+ quality: 'Good',
663
+ bestFor: 'Product demos, animated diagrams'
664
+ }
665
+ };
666
+
667
+ return info[service] || null;
668
+ }