myaidev-method 0.2.18 → 0.2.22
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/.claude/mcp/sparc-orchestrator-server.js +0 -0
- package/.claude/mcp/wordpress-server.js +0 -0
- package/CHANGELOG.md +145 -0
- package/README.md +205 -13
- package/TECHNICAL_ARCHITECTURE.md +64 -2
- package/bin/cli.js +169 -2
- package/dist/mcp/mcp-config.json +138 -1
- package/dist/mcp/openstack-server.js +1607 -0
- package/package.json +2 -2
- package/src/config/workflows.js +532 -0
- package/src/lib/payloadcms-utils.js +343 -10
- package/src/lib/visual-generation-utils.js +445 -294
- package/src/lib/workflow-installer.js +512 -0
- package/src/libs/security/authorization-checker.js +606 -0
- package/src/mcp/openstack-server.js +1607 -0
- package/src/scripts/openstack-setup.sh +110 -0
- package/src/scripts/security/environment-detect.js +425 -0
- package/src/templates/claude/agents/openstack-vm-manager.md +281 -0
- package/src/templates/claude/agents/osint-researcher.md +1075 -0
- package/src/templates/claude/agents/penetration-tester.md +908 -0
- package/src/templates/claude/agents/security-auditor.md +244 -0
- package/src/templates/claude/agents/security-setup.md +1094 -0
- package/src/templates/claude/agents/webapp-security-tester.md +581 -0
- package/src/templates/claude/commands/myai-configure.md +84 -0
- package/src/templates/claude/commands/myai-openstack.md +229 -0
- package/src/templates/claude/commands/sc:security-exploit.md +464 -0
- package/src/templates/claude/commands/sc:security-recon.md +281 -0
- package/src/templates/claude/commands/sc:security-report.md +756 -0
- package/src/templates/claude/commands/sc:security-scan.md +441 -0
- package/src/templates/claude/commands/sc:security-setup.md +501 -0
- package/src/templates/claude/mcp_config.json +44 -0
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
* Visual Content Generation Utilities
|
|
3
3
|
*
|
|
4
4
|
* Provides image and video generation capabilities using:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
5
|
+
*
|
|
6
|
+
* RECOMMENDED SOTA MODELS:
|
|
7
|
+
* - Google Gemini 3.0 Pro Image ("Nano Banana") - fast, cost-effective
|
|
8
|
+
* - OpenAI GPT Image 1.5 - state-of-the-art quality, best text rendering
|
|
9
|
+
*
|
|
10
|
+
* ADDITIONAL MODELS:
|
|
11
|
+
* - Google Imagen 3 - premium quality via Gemini API
|
|
12
|
+
* - Black Forest Labs FLUX 2 (pro, flex, dev) - excellent quality
|
|
13
|
+
* - Google Veo 3 - latest video generation
|
|
14
|
+
*
|
|
15
|
+
* Authentication: Uses simple API keys (GEMINI_API_KEY, OPENAI_API_KEY, FAL_KEY)
|
|
9
16
|
*
|
|
10
17
|
* Platform support: Claude Code, Gemini CLI, Codex CLI
|
|
11
18
|
*
|
|
@@ -16,92 +23,91 @@ import fetch from 'node-fetch';
|
|
|
16
23
|
import fs from 'fs-extra';
|
|
17
24
|
import path from 'path';
|
|
18
25
|
import dotenv from 'dotenv';
|
|
19
|
-
import { GoogleAuth } from 'google-auth-library';
|
|
20
26
|
|
|
21
27
|
dotenv.config();
|
|
22
28
|
|
|
23
29
|
// API Configuration
|
|
24
|
-
const
|
|
30
|
+
const GEMINI_API_BASE = 'https://generativelanguage.googleapis.com/v1beta';
|
|
25
31
|
const OPENAI_API_BASE = 'https://api.openai.com/v1';
|
|
26
32
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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();
|
|
33
|
+
// Gemini Models for image generation
|
|
34
|
+
const GEMINI_IMAGE_MODEL = 'gemini-3-pro-image-preview'; // Gemini 3.0 "Nano Banana" preview
|
|
35
|
+
const GEMINI_IMAGEN_MODEL = 'imagen-3.0-generate-002'; // Imagen via Gemini API
|
|
42
36
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
// OpenAI GPT Image Models (SOTA)
|
|
38
|
+
const OPENAI_IMAGE_MODELS = {
|
|
39
|
+
'gpt-image-1.5': 'gpt-image-1.5', // State-of-the-art (recommended)
|
|
40
|
+
'gpt-image-1': 'gpt-image-1', // Main model
|
|
41
|
+
'gpt-image-1-mini': 'gpt-image-1-mini' // Cost-effective option
|
|
42
|
+
};
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
// FLUX 2 Models (via Fal.ai or BFL API)
|
|
45
|
+
const FLUX2_MODELS = {
|
|
46
|
+
'flux2-pro': 'fal-ai/flux-2/pro', // State-of-the-art quality, fastest, lowest cost
|
|
47
|
+
'flux2-flex': 'fal-ai/flux-2/flex', // Developer-controlled parameters
|
|
48
|
+
'flux2-dev': 'fal-ai/flux-2/dev', // 32B open-weight model
|
|
49
|
+
// Legacy FLUX 1.x models (still available)
|
|
50
|
+
'flux-pro': 'fal-ai/flux-pro/v1.1-ultra',
|
|
51
|
+
'flux-dev': 'fal-ai/flux/dev'
|
|
52
|
+
};
|
|
52
53
|
|
|
53
|
-
// Pricing (USD per image/video)
|
|
54
|
+
// Pricing (USD per image/video)
|
|
54
55
|
const PRICING = {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
56
|
+
// SOTA Models (Recommended)
|
|
57
|
+
gemini: 0.02, // Gemini 3.0 Pro Image "Nano Banana" - fast, cheap
|
|
58
|
+
'gpt-image-1.5': 0.19, // OpenAI GPT Image 1.5 - SOTA quality (high quality)
|
|
59
|
+
'gpt-image-1.5-medium': 0.07, // GPT Image 1.5 medium quality
|
|
60
|
+
'gpt-image-1.5-low': 0.02, // GPT Image 1.5 low quality
|
|
61
|
+
'gpt-image-1': 0.19, // OpenAI GPT Image 1 (high quality)
|
|
62
|
+
'gpt-image-1-mini': 0.02, // OpenAI GPT Image 1 Mini - budget option
|
|
63
|
+
// Additional Models
|
|
64
|
+
imagen: 0.03, // Imagen 3 (Gemini API)
|
|
65
|
+
// FLUX 2 pricing
|
|
66
|
+
flux2_pro: 0.05, // FLUX 2 Pro
|
|
67
|
+
flux2_flex: 0.04, // FLUX 2 Flex
|
|
68
|
+
flux2_dev: 0.025, // FLUX 2 Dev
|
|
69
|
+
// Legacy FLUX 1.x
|
|
70
|
+
flux_pro: 0.06, // FLUX Pro v1.1 Ultra
|
|
71
|
+
flux_dev: 0.025, // FLUX Dev (per megapixel)
|
|
72
|
+
// Video
|
|
73
|
+
veo3: 0.40 // Veo 3 (per second)
|
|
69
74
|
};
|
|
70
75
|
|
|
71
76
|
/**
|
|
72
77
|
* Validate that required API keys are configured
|
|
73
78
|
*
|
|
74
79
|
* @returns {Object} Validation results
|
|
75
|
-
* @returns {boolean} hasGoogle - Google API key is configured
|
|
76
|
-
* @returns {boolean} hasOpenAI - OpenAI API key is configured
|
|
77
|
-
* @returns {boolean} hasAny - At least one API key is configured
|
|
78
|
-
* @returns {Array<string>} availableServices - List of available services
|
|
79
80
|
*/
|
|
80
81
|
export function validateAPIKeys() {
|
|
81
|
-
|
|
82
|
+
// Support both GEMINI_API_KEY (preferred) and GOOGLE_API_KEY (legacy)
|
|
83
|
+
const geminiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
82
84
|
const openaiKey = process.env.OPENAI_API_KEY;
|
|
83
85
|
const falKey = process.env.FAL_KEY;
|
|
86
|
+
const bflKey = process.env.BFL_API_KEY; // Black Forest Labs direct API
|
|
84
87
|
|
|
85
|
-
const
|
|
88
|
+
const hasGemini = !!(geminiKey && geminiKey.length > 20);
|
|
86
89
|
const hasOpenAI = !!(openaiKey && openaiKey.length > 20);
|
|
87
90
|
const hasFal = !!(falKey && falKey.length > 20);
|
|
91
|
+
const hasBFL = !!(bflKey && bflKey.length > 20);
|
|
88
92
|
|
|
89
93
|
const availableServices = [];
|
|
90
|
-
if (
|
|
91
|
-
availableServices.push('gemini', 'imagen'
|
|
94
|
+
if (hasGemini) {
|
|
95
|
+
availableServices.push('gemini', 'imagen');
|
|
92
96
|
}
|
|
93
97
|
if (hasOpenAI) {
|
|
94
|
-
availableServices.push('
|
|
98
|
+
availableServices.push('gpt-image-1.5', 'gpt-image-1', 'gpt-image-1-mini');
|
|
95
99
|
}
|
|
96
|
-
if (hasFal) {
|
|
97
|
-
availableServices.push('
|
|
100
|
+
if (hasFal || hasBFL) {
|
|
101
|
+
availableServices.push('flux2-pro', 'flux2-flex', 'flux2-dev', 'flux-pro', 'flux-dev', 'veo3');
|
|
98
102
|
}
|
|
99
103
|
|
|
100
104
|
return {
|
|
101
|
-
|
|
105
|
+
hasGemini,
|
|
106
|
+
hasGoogle: hasGemini, // Legacy compatibility
|
|
102
107
|
hasOpenAI,
|
|
103
108
|
hasFal,
|
|
104
|
-
|
|
109
|
+
hasBFL,
|
|
110
|
+
hasAny: hasGemini || hasOpenAI || hasFal || hasBFL,
|
|
105
111
|
availableServices
|
|
106
112
|
};
|
|
107
113
|
}
|
|
@@ -109,14 +115,12 @@ export function validateAPIKeys() {
|
|
|
109
115
|
/**
|
|
110
116
|
* Estimate cost for image/video generation
|
|
111
117
|
*
|
|
112
|
-
* @param {string} service - Service name
|
|
118
|
+
* @param {string} service - Service name
|
|
113
119
|
* @param {Object} options - Generation options
|
|
114
|
-
* @param {string} options.quality - Quality level (standard, hd)
|
|
115
|
-
* @param {string} options.size - Image size
|
|
116
120
|
* @returns {number} Estimated cost in USD
|
|
117
121
|
*/
|
|
118
122
|
export function estimateCost(service, options = {}) {
|
|
119
|
-
const { quality = 'high'
|
|
123
|
+
const { quality = 'high' } = options;
|
|
120
124
|
|
|
121
125
|
switch (service) {
|
|
122
126
|
case 'gemini':
|
|
@@ -125,19 +129,26 @@ export function estimateCost(service, options = {}) {
|
|
|
125
129
|
case 'imagen':
|
|
126
130
|
return PRICING.imagen;
|
|
127
131
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
if (quality === 'low')
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return PRICING.dalle_medium;
|
|
134
|
-
} else if (quality === 'high' || quality === 'hd') {
|
|
135
|
-
return PRICING.dalle_high;
|
|
136
|
-
}
|
|
137
|
-
return PRICING.dalle_high; // default to high quality
|
|
132
|
+
// OpenAI GPT Image models - cost varies by quality
|
|
133
|
+
case 'gpt-image-1.5':
|
|
134
|
+
if (quality === 'low') return PRICING['gpt-image-1.5-low'];
|
|
135
|
+
if (quality === 'medium') return PRICING['gpt-image-1.5-medium'];
|
|
136
|
+
return PRICING['gpt-image-1.5']; // high quality default
|
|
138
137
|
|
|
139
|
-
case '
|
|
140
|
-
return PRICING
|
|
138
|
+
case 'gpt-image-1':
|
|
139
|
+
return PRICING['gpt-image-1'];
|
|
140
|
+
|
|
141
|
+
case 'gpt-image-1-mini':
|
|
142
|
+
return PRICING['gpt-image-1-mini'];
|
|
143
|
+
|
|
144
|
+
case 'flux2-pro':
|
|
145
|
+
return PRICING.flux2_pro;
|
|
146
|
+
|
|
147
|
+
case 'flux2-flex':
|
|
148
|
+
return PRICING.flux2_flex;
|
|
149
|
+
|
|
150
|
+
case 'flux2-dev':
|
|
151
|
+
return PRICING.flux2_dev;
|
|
141
152
|
|
|
142
153
|
case 'flux':
|
|
143
154
|
case 'flux-pro':
|
|
@@ -166,7 +177,7 @@ export function selectBestService(preferred = 'gemini') {
|
|
|
166
177
|
const { availableServices, hasAny } = validateAPIKeys();
|
|
167
178
|
|
|
168
179
|
if (!hasAny) {
|
|
169
|
-
throw new Error('No API keys configured.
|
|
180
|
+
throw new Error('No API keys configured. Set GEMINI_API_KEY, OPENAI_API_KEY, or FAL_KEY in your environment.');
|
|
170
181
|
}
|
|
171
182
|
|
|
172
183
|
// Return preferred service if available
|
|
@@ -179,38 +190,44 @@ export function selectBestService(preferred = 'gemini') {
|
|
|
179
190
|
}
|
|
180
191
|
|
|
181
192
|
/**
|
|
182
|
-
* Generate image using Google Gemini
|
|
183
|
-
*
|
|
193
|
+
* Generate image using Google Gemini API
|
|
194
|
+
* Uses gemini-3-pro-image-preview model ("Nano Banana") with simple API key auth
|
|
184
195
|
*
|
|
185
196
|
* @param {string} prompt - Image description
|
|
186
197
|
* @param {Object} options - Generation options
|
|
187
|
-
* @param {
|
|
198
|
+
* @param {string} options.imageSize - Image size (1K, 2K)
|
|
188
199
|
* @param {number} options.maxRetries - Maximum retry attempts
|
|
189
200
|
* @returns {Promise<Object>} Generated image data
|
|
190
201
|
*/
|
|
191
202
|
export async function generateImageGemini(prompt, options = {}) {
|
|
192
203
|
const {
|
|
193
|
-
|
|
204
|
+
imageSize = '1K',
|
|
194
205
|
maxRetries = 3
|
|
195
206
|
} = options;
|
|
196
207
|
|
|
197
|
-
const apiKey = process.env.GOOGLE_API_KEY;
|
|
208
|
+
const apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
198
209
|
if (!apiKey) {
|
|
199
|
-
throw new Error('
|
|
210
|
+
throw new Error('GEMINI_API_KEY not configured. Set GEMINI_API_KEY in your environment.');
|
|
200
211
|
}
|
|
201
212
|
|
|
202
|
-
const endpoint = `${
|
|
213
|
+
const endpoint = `${GEMINI_API_BASE}/models/${GEMINI_IMAGE_MODEL}:generateContent`;
|
|
203
214
|
|
|
204
215
|
const requestBody = {
|
|
205
|
-
contents: [
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
216
|
+
contents: [
|
|
217
|
+
{
|
|
218
|
+
role: 'user',
|
|
219
|
+
parts: [
|
|
220
|
+
{
|
|
221
|
+
text: prompt
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
}
|
|
225
|
+
],
|
|
210
226
|
generationConfig: {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
227
|
+
responseModalities: ['IMAGE', 'TEXT'],
|
|
228
|
+
imageConfig: {
|
|
229
|
+
image_size: imageSize
|
|
230
|
+
}
|
|
214
231
|
}
|
|
215
232
|
};
|
|
216
233
|
|
|
@@ -232,21 +249,38 @@ export async function generateImageGemini(prompt, options = {}) {
|
|
|
232
249
|
|
|
233
250
|
const data = await response.json();
|
|
234
251
|
|
|
235
|
-
//
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
252
|
+
// Handle streaming response format (array of candidates)
|
|
253
|
+
const candidates = Array.isArray(data) ? data : (data.candidates || [data]);
|
|
254
|
+
|
|
255
|
+
for (const candidate of candidates) {
|
|
256
|
+
const content = candidate.content || candidate;
|
|
257
|
+
const parts = content.parts || [];
|
|
258
|
+
|
|
259
|
+
for (const part of parts) {
|
|
260
|
+
// Check for inline image data
|
|
261
|
+
if (part.inlineData && part.inlineData.data) {
|
|
262
|
+
return {
|
|
263
|
+
data: part.inlineData.data,
|
|
264
|
+
mimeType: part.inlineData.mimeType || 'image/png',
|
|
265
|
+
service: 'gemini',
|
|
266
|
+
model: GEMINI_IMAGE_MODEL,
|
|
267
|
+
cost: PRICING.gemini
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Check for file data reference
|
|
272
|
+
if (part.fileData && part.fileData.fileUri) {
|
|
273
|
+
const imageResponse = await fetch(part.fileData.fileUri);
|
|
274
|
+
const imageBuffer = await imageResponse.arrayBuffer();
|
|
275
|
+
const base64Data = Buffer.from(imageBuffer).toString('base64');
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
data: base64Data,
|
|
279
|
+
mimeType: part.fileData.mimeType || 'image/png',
|
|
280
|
+
service: 'gemini',
|
|
281
|
+
model: GEMINI_IMAGE_MODEL,
|
|
282
|
+
cost: PRICING.gemini
|
|
283
|
+
};
|
|
250
284
|
}
|
|
251
285
|
}
|
|
252
286
|
}
|
|
@@ -258,7 +292,7 @@ export async function generateImageGemini(prompt, options = {}) {
|
|
|
258
292
|
|
|
259
293
|
if (attempt < maxRetries) {
|
|
260
294
|
const backoff = Math.pow(2, attempt) * 1000;
|
|
261
|
-
console.log(`⚠️ Gemini attempt ${attempt} failed. Retrying in ${backoff/1000}s...`);
|
|
295
|
+
console.log(`⚠️ Gemini attempt ${attempt} failed: ${error.message}. Retrying in ${backoff / 1000}s...`);
|
|
262
296
|
await sleep(backoff);
|
|
263
297
|
}
|
|
264
298
|
}
|
|
@@ -268,59 +302,45 @@ export async function generateImageGemini(prompt, options = {}) {
|
|
|
268
302
|
}
|
|
269
303
|
|
|
270
304
|
/**
|
|
271
|
-
* Generate image using Google Imagen
|
|
272
|
-
* Premium quality image generation
|
|
273
|
-
*
|
|
274
|
-
* Requires Vertex AI setup:
|
|
275
|
-
* - GOOGLE_CLOUD_PROJECT_ID environment variable
|
|
276
|
-
* - GOOGLE_CLOUD_LOCATION environment variable (default: us-central1)
|
|
277
|
-
* - GOOGLE_APPLICATION_CREDENTIALS pointing to service account key JSON
|
|
305
|
+
* Generate image using Google Imagen 3 (via Gemini API)
|
|
306
|
+
* Premium quality image generation with simple API key authentication
|
|
278
307
|
*
|
|
279
308
|
* @param {string} prompt - Image description
|
|
280
309
|
* @param {Object} options - Generation options
|
|
281
|
-
* @param {string} options.
|
|
310
|
+
* @param {string} options.aspectRatio - Aspect ratio (1:1, 16:9, 9:16, 4:3, 3:4)
|
|
311
|
+
* @param {number} options.numberOfImages - Number of images to generate (1-4)
|
|
282
312
|
* @param {number} options.maxRetries - Maximum retry attempts
|
|
283
313
|
* @returns {Promise<Object>} Generated image data
|
|
284
314
|
*/
|
|
285
315
|
export async function generateImageImagen(prompt, options = {}) {
|
|
286
316
|
const {
|
|
287
|
-
|
|
317
|
+
aspectRatio = '1:1',
|
|
318
|
+
numberOfImages = 1,
|
|
288
319
|
maxRetries = 3
|
|
289
320
|
} = options;
|
|
290
321
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
if (!projectId) {
|
|
296
|
-
throw new Error('GOOGLE_CLOUD_PROJECT_ID not configured. Set up Vertex AI credentials.');
|
|
322
|
+
const apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
323
|
+
if (!apiKey) {
|
|
324
|
+
throw new Error('GEMINI_API_KEY not configured. Set GEMINI_API_KEY in your environment.');
|
|
297
325
|
}
|
|
298
326
|
|
|
299
|
-
|
|
300
|
-
const endpoint = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/imagen-4.0-generate-001:predict`;
|
|
327
|
+
const endpoint = `${GEMINI_API_BASE}/models/${GEMINI_IMAGEN_MODEL}:generateImages`;
|
|
301
328
|
|
|
302
329
|
const requestBody = {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
aspectRatio: size === '1024x1024' ? '1:1' : (size.includes('1792') ? '16:9' : '1:1'),
|
|
309
|
-
safetyFilterLevel: 'block_some',
|
|
310
|
-
personGeneration: 'allow_adult'
|
|
330
|
+
prompt: prompt,
|
|
331
|
+
config: {
|
|
332
|
+
numberOfImages: Math.min(numberOfImages, 4),
|
|
333
|
+
aspectRatio: aspectRatio,
|
|
334
|
+
safetyFilterLevel: 'BLOCK_MEDIUM_AND_ABOVE'
|
|
311
335
|
}
|
|
312
336
|
};
|
|
313
337
|
|
|
314
338
|
let lastError;
|
|
315
339
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
316
340
|
try {
|
|
317
|
-
|
|
318
|
-
const token = await getVertexAIToken();
|
|
319
|
-
|
|
320
|
-
const response = await fetch(endpoint, {
|
|
341
|
+
const response = await fetch(`${endpoint}?key=${apiKey}`, {
|
|
321
342
|
method: 'POST',
|
|
322
343
|
headers: {
|
|
323
|
-
'Authorization': `Bearer ${token}`,
|
|
324
344
|
'Content-Type': 'application/json'
|
|
325
345
|
},
|
|
326
346
|
body: JSON.stringify(requestBody)
|
|
@@ -333,15 +353,31 @@ export async function generateImageImagen(prompt, options = {}) {
|
|
|
333
353
|
|
|
334
354
|
const data = await response.json();
|
|
335
355
|
|
|
336
|
-
|
|
337
|
-
|
|
356
|
+
// Imagen returns images array with base64 encoded data
|
|
357
|
+
if (data.generatedImages && data.generatedImages[0]) {
|
|
358
|
+
const image = data.generatedImages[0];
|
|
338
359
|
|
|
339
|
-
|
|
340
|
-
if (prediction.bytesBase64Encoded) {
|
|
360
|
+
if (image.image && image.image.imageBytes) {
|
|
341
361
|
return {
|
|
342
|
-
data:
|
|
343
|
-
mimeType:
|
|
362
|
+
data: image.image.imageBytes,
|
|
363
|
+
mimeType: 'image/png',
|
|
344
364
|
service: 'imagen',
|
|
365
|
+
model: GEMINI_IMAGEN_MODEL,
|
|
366
|
+
cost: PRICING.imagen
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Alternative response format
|
|
372
|
+
if (data.images && data.images[0]) {
|
|
373
|
+
const image = data.images[0];
|
|
374
|
+
|
|
375
|
+
if (image.bytesBase64Encoded || image.imageBytes) {
|
|
376
|
+
return {
|
|
377
|
+
data: image.bytesBase64Encoded || image.imageBytes,
|
|
378
|
+
mimeType: 'image/png',
|
|
379
|
+
service: 'imagen',
|
|
380
|
+
model: GEMINI_IMAGEN_MODEL,
|
|
345
381
|
cost: PRICING.imagen
|
|
346
382
|
};
|
|
347
383
|
}
|
|
@@ -354,7 +390,7 @@ export async function generateImageImagen(prompt, options = {}) {
|
|
|
354
390
|
|
|
355
391
|
if (attempt < maxRetries) {
|
|
356
392
|
const backoff = Math.pow(2, attempt) * 1000;
|
|
357
|
-
console.log(`⚠️ Imagen attempt ${attempt} failed. Retrying in ${backoff/1000}s...`);
|
|
393
|
+
console.log(`⚠️ Imagen attempt ${attempt} failed: ${error.message}. Retrying in ${backoff / 1000}s...`);
|
|
358
394
|
await sleep(backoff);
|
|
359
395
|
}
|
|
360
396
|
}
|
|
@@ -364,42 +400,56 @@ export async function generateImageImagen(prompt, options = {}) {
|
|
|
364
400
|
}
|
|
365
401
|
|
|
366
402
|
/**
|
|
367
|
-
* Generate image using OpenAI
|
|
368
|
-
*
|
|
403
|
+
* Generate image using OpenAI GPT Image API
|
|
404
|
+
* State-of-the-art image generation with best text rendering
|
|
405
|
+
*
|
|
406
|
+
* Features:
|
|
407
|
+
* - Best-in-class text rendering in images
|
|
408
|
+
* - Multiple quality tiers (low, medium, high)
|
|
409
|
+
* - Transparency support (PNG with transparent background)
|
|
410
|
+
* - Multiple output formats (PNG, JPEG, WebP)
|
|
369
411
|
*
|
|
370
412
|
* @param {string} prompt - Image description
|
|
371
413
|
* @param {Object} options - Generation options
|
|
372
|
-
* @param {string} options.
|
|
373
|
-
* @param {string} options.
|
|
374
|
-
* @param {string} options.
|
|
414
|
+
* @param {string} options.model - Model (gpt-image-1.5, gpt-image-1, gpt-image-1-mini)
|
|
415
|
+
* @param {string} options.size - Image size (1024x1024, 1536x1024, 1024x1536, auto)
|
|
416
|
+
* @param {string} options.quality - Quality level (low, medium, high, auto)
|
|
417
|
+
* @param {string} options.outputFormat - Output format (png, jpeg, webp)
|
|
418
|
+
* @param {string} options.background - Background type (transparent, opaque, auto)
|
|
375
419
|
* @param {number} options.maxRetries - Maximum retry attempts
|
|
376
|
-
* @returns {Promise<Object>} Generated image data
|
|
420
|
+
* @returns {Promise<Object>} Generated image data
|
|
377
421
|
*/
|
|
378
|
-
export async function
|
|
422
|
+
export async function generateImageOpenAI(prompt, options = {}) {
|
|
379
423
|
const {
|
|
424
|
+
model = 'gpt-image-1.5',
|
|
380
425
|
size = '1024x1024',
|
|
381
|
-
quality = 'high',
|
|
426
|
+
quality = 'high',
|
|
427
|
+
outputFormat = 'png',
|
|
428
|
+
background = 'auto',
|
|
382
429
|
maxRetries = 3
|
|
383
430
|
} = options;
|
|
384
431
|
|
|
385
432
|
const apiKey = process.env.OPENAI_API_KEY;
|
|
386
433
|
if (!apiKey) {
|
|
387
|
-
throw new Error('OPENAI_API_KEY not configured');
|
|
434
|
+
throw new Error('OPENAI_API_KEY not configured. Get your key from https://platform.openai.com/api-keys');
|
|
388
435
|
}
|
|
389
436
|
|
|
390
437
|
const endpoint = `${OPENAI_API_BASE}/images/generations`;
|
|
391
438
|
|
|
392
|
-
// Try GPT-Image-1 first, fall back to DALL-E 3 if not available
|
|
393
|
-
const model = 'dall-e-3'; // Will use gpt-image-1 once org is verified
|
|
394
|
-
|
|
395
439
|
const requestBody = {
|
|
396
|
-
model: model,
|
|
440
|
+
model: OPENAI_IMAGE_MODELS[model] || model,
|
|
397
441
|
prompt: prompt,
|
|
398
442
|
n: 1,
|
|
399
443
|
size: size,
|
|
400
|
-
|
|
444
|
+
quality: quality,
|
|
445
|
+
output_format: outputFormat
|
|
401
446
|
};
|
|
402
447
|
|
|
448
|
+
// Add background for PNG format (transparency support)
|
|
449
|
+
if (outputFormat === 'png' && background !== 'auto') {
|
|
450
|
+
requestBody.background = background;
|
|
451
|
+
}
|
|
452
|
+
|
|
403
453
|
let lastError;
|
|
404
454
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
405
455
|
try {
|
|
@@ -413,37 +463,61 @@ export async function generateImageDALLE(prompt, options = {}) {
|
|
|
413
463
|
});
|
|
414
464
|
|
|
415
465
|
if (!response.ok) {
|
|
416
|
-
const
|
|
417
|
-
|
|
466
|
+
const errorText = await response.text();
|
|
467
|
+
let errorMessage = `OpenAI API error: ${response.status}`;
|
|
468
|
+
try {
|
|
469
|
+
const errorData = JSON.parse(errorText);
|
|
470
|
+
errorMessage = errorData.error?.message || errorMessage;
|
|
471
|
+
} catch {
|
|
472
|
+
errorMessage = `${errorMessage} - ${errorText}`;
|
|
473
|
+
}
|
|
474
|
+
throw new Error(errorMessage);
|
|
418
475
|
}
|
|
419
476
|
|
|
420
477
|
const data = await response.json();
|
|
421
478
|
|
|
479
|
+
// OpenAI returns base64-encoded image data
|
|
422
480
|
if (data.data && data.data[0]) {
|
|
423
|
-
const
|
|
481
|
+
const imageData = data.data[0];
|
|
424
482
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
483
|
+
// Handle base64 response (primary)
|
|
484
|
+
if (imageData.b64_json) {
|
|
485
|
+
return {
|
|
486
|
+
data: imageData.b64_json,
|
|
487
|
+
mimeType: outputFormat === 'jpeg' ? 'image/jpeg' : outputFormat === 'webp' ? 'image/webp' : 'image/png',
|
|
488
|
+
service: 'openai',
|
|
489
|
+
model: model,
|
|
490
|
+
cost: estimateCost(model, { quality }),
|
|
491
|
+
revisedPrompt: imageData.revised_prompt
|
|
492
|
+
};
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Handle URL response (fallback)
|
|
496
|
+
if (imageData.url) {
|
|
497
|
+
const imageResponse = await fetch(imageData.url);
|
|
498
|
+
const imageBuffer = await imageResponse.arrayBuffer();
|
|
499
|
+
const base64Data = Buffer.from(imageBuffer).toString('base64');
|
|
500
|
+
|
|
501
|
+
return {
|
|
502
|
+
data: base64Data,
|
|
503
|
+
mimeType: outputFormat === 'jpeg' ? 'image/jpeg' : outputFormat === 'webp' ? 'image/webp' : 'image/png',
|
|
504
|
+
service: 'openai',
|
|
505
|
+
model: model,
|
|
506
|
+
cost: estimateCost(model, { quality }),
|
|
507
|
+
revisedPrompt: imageData.revised_prompt
|
|
508
|
+
};
|
|
509
|
+
}
|
|
432
510
|
}
|
|
433
511
|
|
|
434
|
-
throw new Error('No image data in
|
|
512
|
+
throw new Error('No image data in OpenAI response');
|
|
435
513
|
|
|
436
514
|
} catch (error) {
|
|
437
515
|
lastError = error;
|
|
438
516
|
|
|
439
|
-
if (
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
await sleep(backoff);
|
|
444
|
-
}
|
|
445
|
-
} else {
|
|
446
|
-
throw error; // Don't retry on other errors
|
|
517
|
+
if (attempt < maxRetries) {
|
|
518
|
+
const backoff = Math.pow(2, attempt) * 1000;
|
|
519
|
+
console.log(`⚠️ OpenAI attempt ${attempt} failed: ${error.message}. Retrying in ${backoff / 1000}s...`);
|
|
520
|
+
await sleep(backoff);
|
|
447
521
|
}
|
|
448
522
|
}
|
|
449
523
|
}
|
|
@@ -452,83 +526,110 @@ export async function generateImageDALLE(prompt, options = {}) {
|
|
|
452
526
|
}
|
|
453
527
|
|
|
454
528
|
/**
|
|
455
|
-
* Generate
|
|
456
|
-
*
|
|
529
|
+
* Generate image using FLUX 2 (via Fal.ai)
|
|
530
|
+
* State-of-the-art image generation from Black Forest Labs
|
|
457
531
|
*
|
|
458
|
-
*
|
|
532
|
+
* Features:
|
|
533
|
+
* - Multi-reference support (up to 10 images)
|
|
534
|
+
* - Enhanced photorealism
|
|
535
|
+
* - Complex typography and UI mockups
|
|
536
|
+
* - Image editing up to 4 megapixels
|
|
537
|
+
*
|
|
538
|
+
* @param {string} prompt - Image description
|
|
459
539
|
* @param {Object} options - Generation options
|
|
460
|
-
* @param {
|
|
461
|
-
* @param {string} options.
|
|
540
|
+
* @param {string} options.model - FLUX 2 model (flux2-pro, flux2-flex, flux2-dev)
|
|
541
|
+
* @param {string} options.size - Image size (square, landscape, portrait)
|
|
542
|
+
* @param {number} options.steps - Number of inference steps (flux2-flex only)
|
|
543
|
+
* @param {number} options.guidance - Guidance scale (flux2-flex only)
|
|
544
|
+
* @param {Array<string>} options.referenceImages - Reference image URLs (up to 10)
|
|
462
545
|
* @param {number} options.maxRetries - Maximum retry attempts
|
|
463
|
-
* @returns {Promise<Object>} Generated
|
|
546
|
+
* @returns {Promise<Object>} Generated image data
|
|
464
547
|
*/
|
|
465
|
-
export async function
|
|
548
|
+
export async function generateImageFlux2(prompt, options = {}) {
|
|
466
549
|
const {
|
|
467
|
-
|
|
468
|
-
|
|
550
|
+
model = 'flux2-pro',
|
|
551
|
+
size = 'square',
|
|
552
|
+
steps = 28,
|
|
553
|
+
guidance = 3.5,
|
|
554
|
+
referenceImages = [],
|
|
469
555
|
maxRetries = 3
|
|
470
556
|
} = options;
|
|
471
557
|
|
|
472
|
-
const apiKey = process.env.
|
|
558
|
+
const apiKey = process.env.FAL_KEY || process.env.BFL_API_KEY;
|
|
473
559
|
if (!apiKey) {
|
|
474
|
-
throw new Error('
|
|
560
|
+
throw new Error('FAL_KEY not configured. Get your key from https://fal.ai/dashboard/keys');
|
|
475
561
|
}
|
|
476
562
|
|
|
477
|
-
|
|
563
|
+
// Import fal.ai client
|
|
564
|
+
const { fal } = await import('@fal-ai/client');
|
|
565
|
+
fal.config({ credentials: apiKey });
|
|
478
566
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
567
|
+
// Get endpoint for model
|
|
568
|
+
const endpoint = FLUX2_MODELS[model] || FLUX2_MODELS['flux2-pro'];
|
|
569
|
+
|
|
570
|
+
// Build input based on model capabilities
|
|
571
|
+
const input = {
|
|
572
|
+
prompt: prompt,
|
|
573
|
+
image_size: size === '1024x1024' ? 'square' : size,
|
|
574
|
+
num_images: 1
|
|
488
575
|
};
|
|
489
576
|
|
|
577
|
+
// FLUX 2 Flex supports custom parameters
|
|
578
|
+
if (model === 'flux2-flex') {
|
|
579
|
+
input.num_inference_steps = steps;
|
|
580
|
+
input.guidance_scale = guidance;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Add reference images if provided (FLUX 2 multi-reference feature)
|
|
584
|
+
if (referenceImages.length > 0) {
|
|
585
|
+
input.reference_images = referenceImages.slice(0, 10); // Max 10
|
|
586
|
+
}
|
|
587
|
+
|
|
490
588
|
let lastError;
|
|
491
589
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
492
590
|
try {
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
'Content-Type': 'application/json'
|
|
497
|
-
},
|
|
498
|
-
body: JSON.stringify(requestBody)
|
|
591
|
+
const result = await fal.subscribe(endpoint, {
|
|
592
|
+
input,
|
|
593
|
+
logs: false
|
|
499
594
|
});
|
|
500
595
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
}
|
|
596
|
+
// Extract image from result
|
|
597
|
+
let imageUrl;
|
|
598
|
+
let contentType = 'image/png';
|
|
505
599
|
|
|
506
|
-
|
|
600
|
+
if (result.data?.images?.[0]) {
|
|
601
|
+
imageUrl = result.data.images[0].url;
|
|
602
|
+
contentType = result.data.images[0].content_type || 'image/png';
|
|
603
|
+
} else if (result.images?.[0]) {
|
|
604
|
+
imageUrl = result.images[0].url;
|
|
605
|
+
contentType = result.images[0].content_type || 'image/png';
|
|
606
|
+
} else if (result.image?.url) {
|
|
607
|
+
imageUrl = result.image.url;
|
|
608
|
+
}
|
|
507
609
|
|
|
508
|
-
if (
|
|
509
|
-
|
|
610
|
+
if (imageUrl) {
|
|
611
|
+
// Fetch and convert to base64
|
|
612
|
+
const imageResponse = await fetch(imageUrl);
|
|
613
|
+
const imageBuffer = await imageResponse.arrayBuffer();
|
|
614
|
+
const base64Data = Buffer.from(imageBuffer).toString('base64');
|
|
510
615
|
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
cost: PRICING.veo,
|
|
519
|
-
duration: duration
|
|
520
|
-
};
|
|
521
|
-
}
|
|
616
|
+
return {
|
|
617
|
+
data: base64Data,
|
|
618
|
+
mimeType: contentType,
|
|
619
|
+
service: 'flux2',
|
|
620
|
+
model: model,
|
|
621
|
+
cost: PRICING[model.replace('-', '_')] || PRICING.flux2_pro
|
|
622
|
+
};
|
|
522
623
|
}
|
|
523
624
|
|
|
524
|
-
throw new Error('No
|
|
625
|
+
throw new Error('No image data in FLUX 2 response');
|
|
525
626
|
|
|
526
627
|
} catch (error) {
|
|
527
628
|
lastError = error;
|
|
528
629
|
|
|
529
630
|
if (attempt < maxRetries) {
|
|
530
|
-
const backoff = Math.pow(2, attempt) *
|
|
531
|
-
console.log(`⚠️
|
|
631
|
+
const backoff = Math.pow(2, attempt) * 1000;
|
|
632
|
+
console.log(`⚠️ FLUX 2 attempt ${attempt} failed: ${error.message}. Retrying in ${backoff / 1000}s...`);
|
|
532
633
|
await sleep(backoff);
|
|
533
634
|
}
|
|
534
635
|
}
|
|
@@ -538,12 +639,12 @@ export async function generateVideoVeo(prompt, options = {}) {
|
|
|
538
639
|
}
|
|
539
640
|
|
|
540
641
|
/**
|
|
541
|
-
* Generate image using Fal.ai
|
|
542
|
-
*
|
|
642
|
+
* Generate image using legacy FLUX 1.x (via Fal.ai)
|
|
643
|
+
* Still available for backwards compatibility
|
|
543
644
|
*
|
|
544
645
|
* @param {string} prompt - Image description
|
|
545
646
|
* @param {Object} options - Generation options
|
|
546
|
-
* @param {string} options.model -
|
|
647
|
+
* @param {string} options.model - FLUX model (flux-pro, flux-dev)
|
|
547
648
|
* @param {string} options.size - Image size
|
|
548
649
|
* @param {number} options.maxRetries - Maximum retry attempts
|
|
549
650
|
* @returns {Promise<Object>} Generated image data
|
|
@@ -560,21 +661,10 @@ export async function generateImageFal(prompt, options = {}) {
|
|
|
560
661
|
throw new Error('FAL_KEY not configured. Get your key from https://fal.ai/dashboard/keys');
|
|
561
662
|
}
|
|
562
663
|
|
|
563
|
-
// Import fal.ai client
|
|
564
664
|
const { fal } = await import('@fal-ai/client');
|
|
565
|
-
|
|
566
|
-
// Configure credentials
|
|
567
665
|
fal.config({ credentials: apiKey });
|
|
568
666
|
|
|
569
|
-
|
|
570
|
-
const modelMap = {
|
|
571
|
-
'flux-pro': 'fal-ai/flux-pro/v1.1-ultra',
|
|
572
|
-
'flux-dev': 'fal-ai/flux/dev',
|
|
573
|
-
'nano-banana': 'fal-ai/nano-banana',
|
|
574
|
-
'imagen-3-fast': 'fal-ai/fast-imagen'
|
|
575
|
-
};
|
|
576
|
-
|
|
577
|
-
const endpoint = modelMap[model] || modelMap['flux-pro'];
|
|
667
|
+
const endpoint = FLUX2_MODELS[model] || FLUX2_MODELS['flux-pro'];
|
|
578
668
|
|
|
579
669
|
let lastError;
|
|
580
670
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
@@ -588,26 +678,20 @@ export async function generateImageFal(prompt, options = {}) {
|
|
|
588
678
|
logs: false
|
|
589
679
|
});
|
|
590
680
|
|
|
591
|
-
// Fal.ai can return images in different formats
|
|
592
681
|
let imageUrl;
|
|
593
682
|
let contentType = 'image/png';
|
|
594
683
|
|
|
595
|
-
if (result.data
|
|
684
|
+
if (result.data?.images?.[0]) {
|
|
596
685
|
imageUrl = result.data.images[0].url;
|
|
597
686
|
contentType = result.data.images[0].content_type || 'image/png';
|
|
598
|
-
} else if (result.images
|
|
687
|
+
} else if (result.images?.[0]) {
|
|
599
688
|
imageUrl = result.images[0].url;
|
|
600
689
|
contentType = result.images[0].content_type || 'image/png';
|
|
601
|
-
} else if (result.image
|
|
690
|
+
} else if (result.image?.url) {
|
|
602
691
|
imageUrl = result.image.url;
|
|
603
|
-
} else if (result.data && result.data[0] && result.data[0].url) {
|
|
604
|
-
imageUrl = result.data[0].url;
|
|
605
|
-
} else if (typeof result === 'string') {
|
|
606
|
-
imageUrl = result;
|
|
607
692
|
}
|
|
608
693
|
|
|
609
694
|
if (imageUrl) {
|
|
610
|
-
// Fal.ai returns URL, need to fetch and convert to base64
|
|
611
695
|
const imageResponse = await fetch(imageUrl);
|
|
612
696
|
const imageBuffer = await imageResponse.arrayBuffer();
|
|
613
697
|
const base64Data = Buffer.from(imageBuffer).toString('base64');
|
|
@@ -617,7 +701,7 @@ export async function generateImageFal(prompt, options = {}) {
|
|
|
617
701
|
mimeType: contentType,
|
|
618
702
|
service: 'fal',
|
|
619
703
|
model: model,
|
|
620
|
-
cost: PRICING[
|
|
704
|
+
cost: PRICING[model.replace('-', '_')] || PRICING.flux_pro
|
|
621
705
|
};
|
|
622
706
|
}
|
|
623
707
|
|
|
@@ -628,7 +712,7 @@ export async function generateImageFal(prompt, options = {}) {
|
|
|
628
712
|
|
|
629
713
|
if (attempt < maxRetries) {
|
|
630
714
|
const backoff = Math.pow(2, attempt) * 1000;
|
|
631
|
-
console.log(`⚠️ Fal.ai attempt ${attempt} failed. Retrying in ${backoff/1000}s...`);
|
|
715
|
+
console.log(`⚠️ Fal.ai attempt ${attempt} failed: ${error.message}. Retrying in ${backoff / 1000}s...`);
|
|
632
716
|
await sleep(backoff);
|
|
633
717
|
}
|
|
634
718
|
}
|
|
@@ -638,8 +722,8 @@ export async function generateImageFal(prompt, options = {}) {
|
|
|
638
722
|
}
|
|
639
723
|
|
|
640
724
|
/**
|
|
641
|
-
* Generate video using Fal.ai
|
|
642
|
-
* Latest video generation
|
|
725
|
+
* Generate video using Veo 3 (via Fal.ai)
|
|
726
|
+
* Latest video generation with outstanding quality
|
|
643
727
|
*
|
|
644
728
|
* @param {string} prompt - Video description
|
|
645
729
|
* @param {Object} options - Generation options
|
|
@@ -649,7 +733,7 @@ export async function generateImageFal(prompt, options = {}) {
|
|
|
649
733
|
* @param {number} options.maxRetries - Maximum retry attempts
|
|
650
734
|
* @returns {Promise<Object>} Generated video data
|
|
651
735
|
*/
|
|
652
|
-
export async function
|
|
736
|
+
export async function generateVideoVeo3(prompt, options = {}) {
|
|
653
737
|
const {
|
|
654
738
|
model = 'veo3',
|
|
655
739
|
duration = 5,
|
|
@@ -662,10 +746,7 @@ export async function generateVideoFal(prompt, options = {}) {
|
|
|
662
746
|
throw new Error('FAL_KEY not configured. Get your key from https://fal.ai/dashboard/keys');
|
|
663
747
|
}
|
|
664
748
|
|
|
665
|
-
// Import fal.ai client
|
|
666
749
|
const { fal } = await import('@fal-ai/client');
|
|
667
|
-
|
|
668
|
-
// Configure credentials
|
|
669
750
|
fal.config({ credentials: apiKey });
|
|
670
751
|
|
|
671
752
|
const endpoint = model === 'veo3-fast' ? 'fal-ai/veo3-fast' : 'fal-ai/veo3';
|
|
@@ -682,29 +763,30 @@ export async function generateVideoFal(prompt, options = {}) {
|
|
|
682
763
|
logs: false
|
|
683
764
|
});
|
|
684
765
|
|
|
685
|
-
if (result.video
|
|
686
|
-
// Fal.ai returns video URL
|
|
766
|
+
if (result.video?.url) {
|
|
687
767
|
const videoResponse = await fetch(result.video.url);
|
|
688
768
|
const videoBuffer = await videoResponse.arrayBuffer();
|
|
689
769
|
const base64Data = Buffer.from(videoBuffer).toString('base64');
|
|
690
770
|
|
|
691
771
|
return {
|
|
692
772
|
data: base64Data,
|
|
773
|
+
url: result.video.url,
|
|
693
774
|
mimeType: 'video/mp4',
|
|
694
|
-
service: '
|
|
775
|
+
service: 'veo3',
|
|
695
776
|
model: model,
|
|
696
|
-
cost: PRICING.veo3 * duration
|
|
777
|
+
cost: PRICING.veo3 * duration,
|
|
778
|
+
duration: duration
|
|
697
779
|
};
|
|
698
780
|
}
|
|
699
781
|
|
|
700
|
-
throw new Error('No video data in
|
|
782
|
+
throw new Error('No video data in Veo 3 response');
|
|
701
783
|
|
|
702
784
|
} catch (error) {
|
|
703
785
|
lastError = error;
|
|
704
786
|
|
|
705
787
|
if (attempt < maxRetries) {
|
|
706
788
|
const backoff = Math.pow(2, attempt) * 1000;
|
|
707
|
-
console.log(`⚠️
|
|
789
|
+
console.log(`⚠️ Veo 3 attempt ${attempt} failed: ${error.message}. Retrying in ${backoff / 1000}s...`);
|
|
708
790
|
await sleep(backoff);
|
|
709
791
|
}
|
|
710
792
|
}
|
|
@@ -736,7 +818,7 @@ export async function downloadImage(url) {
|
|
|
736
818
|
*
|
|
737
819
|
* @param {string} prompt - Image description
|
|
738
820
|
* @param {Object} options - Generation options
|
|
739
|
-
* @param {string} options.preferredService - Preferred service
|
|
821
|
+
* @param {string} options.preferredService - Preferred service
|
|
740
822
|
* @param {string} options.type - Image type for optimization (hero, illustration, diagram)
|
|
741
823
|
* @returns {Promise<Object>} Generated image data with buffer
|
|
742
824
|
*/
|
|
@@ -763,8 +845,17 @@ export async function generateImage(prompt, options = {}) {
|
|
|
763
845
|
result = await generateImageImagen(enhancedPrompt, serviceOptions);
|
|
764
846
|
break;
|
|
765
847
|
|
|
766
|
-
|
|
767
|
-
|
|
848
|
+
// OpenAI GPT Image models (SOTA)
|
|
849
|
+
case 'gpt-image-1.5':
|
|
850
|
+
case 'gpt-image-1':
|
|
851
|
+
case 'gpt-image-1-mini':
|
|
852
|
+
result = await generateImageOpenAI(enhancedPrompt, { ...serviceOptions, model: service });
|
|
853
|
+
break;
|
|
854
|
+
|
|
855
|
+
case 'flux2-pro':
|
|
856
|
+
case 'flux2-flex':
|
|
857
|
+
case 'flux2-dev':
|
|
858
|
+
result = await generateImageFlux2(enhancedPrompt, { ...serviceOptions, model: service });
|
|
768
859
|
break;
|
|
769
860
|
|
|
770
861
|
case 'flux':
|
|
@@ -780,10 +871,8 @@ export async function generateImage(prompt, options = {}) {
|
|
|
780
871
|
// Convert to buffer
|
|
781
872
|
let buffer;
|
|
782
873
|
if (result.data) {
|
|
783
|
-
// Base64 encoded data
|
|
784
874
|
buffer = Buffer.from(result.data, 'base64');
|
|
785
875
|
} else if (result.url) {
|
|
786
|
-
// Download from URL
|
|
787
876
|
buffer = await downloadImage(result.url);
|
|
788
877
|
} else {
|
|
789
878
|
throw new Error('No image data or URL in response');
|
|
@@ -797,6 +886,21 @@ export async function generateImage(prompt, options = {}) {
|
|
|
797
886
|
};
|
|
798
887
|
}
|
|
799
888
|
|
|
889
|
+
/**
|
|
890
|
+
* Generate video using auto-selected service
|
|
891
|
+
*
|
|
892
|
+
* @param {string} prompt - Video description
|
|
893
|
+
* @param {Object} options - Generation options
|
|
894
|
+
* @returns {Promise<Object>} Generated video data
|
|
895
|
+
*/
|
|
896
|
+
export async function generateVideo(prompt, options = {}) {
|
|
897
|
+
const { preferredService = 'veo3', ...serviceOptions } = options;
|
|
898
|
+
|
|
899
|
+
console.log(`🎬 Generating video using ${preferredService}...`);
|
|
900
|
+
|
|
901
|
+
return await generateVideoVeo3(prompt, serviceOptions);
|
|
902
|
+
}
|
|
903
|
+
|
|
800
904
|
/**
|
|
801
905
|
* Enhance prompt based on image type
|
|
802
906
|
*
|
|
@@ -836,13 +940,14 @@ function sleep(ms) {
|
|
|
836
940
|
export function getServiceInfo(service) {
|
|
837
941
|
const info = {
|
|
838
942
|
gemini: {
|
|
839
|
-
name: 'Gemini
|
|
943
|
+
name: 'Gemini 3.0 Pro Image',
|
|
840
944
|
nickname: 'Nano Banana',
|
|
841
945
|
speed: 'Fast',
|
|
842
946
|
cost: '$0.02/image',
|
|
843
947
|
quality: 'Good',
|
|
844
948
|
bestFor: 'Quick hero images, high volume',
|
|
845
|
-
provider: 'Google
|
|
949
|
+
provider: 'Google Gemini API (API Key)',
|
|
950
|
+
model: GEMINI_IMAGE_MODEL
|
|
846
951
|
},
|
|
847
952
|
imagen: {
|
|
848
953
|
name: 'Imagen 3',
|
|
@@ -851,51 +956,97 @@ export function getServiceInfo(service) {
|
|
|
851
956
|
cost: '$0.03/image',
|
|
852
957
|
quality: 'Excellent',
|
|
853
958
|
bestFor: 'Premium hero images, photorealistic',
|
|
854
|
-
provider: 'Google
|
|
959
|
+
provider: 'Google Gemini API (API Key)',
|
|
960
|
+
model: GEMINI_IMAGEN_MODEL
|
|
855
961
|
},
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
962
|
+
// OpenAI GPT Image Models (SOTA)
|
|
963
|
+
'gpt-image-1.5': {
|
|
964
|
+
name: 'GPT Image 1.5',
|
|
965
|
+
nickname: 'State-of-the-Art',
|
|
859
966
|
speed: 'Medium',
|
|
860
|
-
cost: '$0.
|
|
861
|
-
quality: '
|
|
862
|
-
bestFor: '
|
|
863
|
-
provider: 'OpenAI
|
|
967
|
+
cost: '$0.02-$0.19/image',
|
|
968
|
+
quality: 'Outstanding',
|
|
969
|
+
bestFor: 'Best text rendering, highest quality, transparent backgrounds',
|
|
970
|
+
provider: 'OpenAI',
|
|
971
|
+
model: 'gpt-image-1.5',
|
|
972
|
+
features: ['Best-in-class text rendering', 'Transparency support', 'Multiple quality tiers', 'WebP/JPEG/PNG output'],
|
|
973
|
+
qualityTiers: {
|
|
974
|
+
low: '$0.02/image (~272 tokens)',
|
|
975
|
+
medium: '$0.07/image (~1056 tokens)',
|
|
976
|
+
high: '$0.19/image (~4160 tokens)'
|
|
977
|
+
}
|
|
864
978
|
},
|
|
865
|
-
|
|
866
|
-
name: '
|
|
867
|
-
nickname: '
|
|
868
|
-
speed: '
|
|
869
|
-
cost: '$0.
|
|
979
|
+
'gpt-image-1': {
|
|
980
|
+
name: 'GPT Image 1',
|
|
981
|
+
nickname: 'Premium Quality',
|
|
982
|
+
speed: 'Medium',
|
|
983
|
+
cost: '$0.19/image',
|
|
984
|
+
quality: 'Outstanding',
|
|
985
|
+
bestFor: 'High quality images, text rendering',
|
|
986
|
+
provider: 'OpenAI',
|
|
987
|
+
model: 'gpt-image-1',
|
|
988
|
+
features: ['Excellent text rendering', 'Multiple sizes', 'Transparency support']
|
|
989
|
+
},
|
|
990
|
+
'gpt-image-1-mini': {
|
|
991
|
+
name: 'GPT Image 1 Mini',
|
|
992
|
+
nickname: 'Cost-Effective',
|
|
993
|
+
speed: 'Fast',
|
|
994
|
+
cost: '$0.02/image',
|
|
870
995
|
quality: 'Good',
|
|
871
|
-
bestFor: '
|
|
872
|
-
provider: '
|
|
996
|
+
bestFor: 'Quick images, budget-conscious, high volume',
|
|
997
|
+
provider: 'OpenAI',
|
|
998
|
+
model: 'gpt-image-1-mini',
|
|
999
|
+
features: ['Fast generation', 'Low cost', 'Good quality']
|
|
873
1000
|
},
|
|
874
|
-
|
|
875
|
-
name: 'FLUX Pro
|
|
876
|
-
nickname: '
|
|
1001
|
+
'flux2-pro': {
|
|
1002
|
+
name: 'FLUX 2 Pro',
|
|
1003
|
+
nickname: 'State-of-the-Art',
|
|
1004
|
+
speed: 'Fast',
|
|
1005
|
+
cost: '$0.05/image',
|
|
1006
|
+
quality: 'Outstanding',
|
|
1007
|
+
bestFor: 'Best quality, fastest generation, lowest cost',
|
|
1008
|
+
provider: 'Black Forest Labs (Fal.ai)',
|
|
1009
|
+
model: 'flux-2/pro',
|
|
1010
|
+
features: ['Multi-reference (up to 10 images)', 'Enhanced photorealism', 'Complex typography', 'UI mockups']
|
|
1011
|
+
},
|
|
1012
|
+
'flux2-flex': {
|
|
1013
|
+
name: 'FLUX 2 Flex',
|
|
1014
|
+
nickname: 'Developer Control',
|
|
877
1015
|
speed: 'Medium',
|
|
878
|
-
cost: '$0.
|
|
1016
|
+
cost: '$0.04/image',
|
|
879
1017
|
quality: 'Outstanding',
|
|
880
|
-
bestFor: '
|
|
881
|
-
provider: 'Fal.ai'
|
|
1018
|
+
bestFor: 'Custom parameters, fine-tuned control',
|
|
1019
|
+
provider: 'Black Forest Labs (Fal.ai)',
|
|
1020
|
+
model: 'flux-2/flex',
|
|
1021
|
+
features: ['Custom inference steps', 'Guidance scale control', 'Developer-friendly']
|
|
1022
|
+
},
|
|
1023
|
+
'flux2-dev': {
|
|
1024
|
+
name: 'FLUX 2 Dev',
|
|
1025
|
+
nickname: 'Open-Weight',
|
|
1026
|
+
speed: 'Fast',
|
|
1027
|
+
cost: '$0.025/image',
|
|
1028
|
+
quality: 'Excellent',
|
|
1029
|
+
bestFor: 'Developer workflows, local deployment option',
|
|
1030
|
+
provider: 'Black Forest Labs (Fal.ai)',
|
|
1031
|
+
model: 'flux-2/dev',
|
|
1032
|
+
features: ['32B parameters', 'Open-weight model', 'Local deployment available']
|
|
882
1033
|
},
|
|
883
1034
|
'flux-pro': {
|
|
884
1035
|
name: 'FLUX Pro v1.1 Ultra',
|
|
885
|
-
nickname: 'Premium
|
|
1036
|
+
nickname: 'Legacy Premium',
|
|
886
1037
|
speed: 'Medium',
|
|
887
1038
|
cost: '$0.06/image',
|
|
888
1039
|
quality: 'Outstanding',
|
|
889
|
-
bestFor: 'Premium artistic images
|
|
1040
|
+
bestFor: 'Premium artistic images (legacy)',
|
|
890
1041
|
provider: 'Fal.ai'
|
|
891
1042
|
},
|
|
892
1043
|
'flux-dev': {
|
|
893
1044
|
name: 'FLUX Dev',
|
|
894
|
-
nickname: 'Developer
|
|
1045
|
+
nickname: 'Legacy Developer',
|
|
895
1046
|
speed: 'Fast',
|
|
896
1047
|
cost: '$0.025/MP',
|
|
897
1048
|
quality: 'Excellent',
|
|
898
|
-
bestFor: 'Developer workflows
|
|
1049
|
+
bestFor: 'Developer workflows (legacy)',
|
|
899
1050
|
provider: 'Fal.ai'
|
|
900
1051
|
},
|
|
901
1052
|
veo3: {
|
|
@@ -905,7 +1056,7 @@ export function getServiceInfo(service) {
|
|
|
905
1056
|
cost: '$0.40/second',
|
|
906
1057
|
quality: 'Outstanding',
|
|
907
1058
|
bestFor: 'Premium video content, latest features',
|
|
908
|
-
provider: 'Fal.ai'
|
|
1059
|
+
provider: 'Google (Fal.ai)'
|
|
909
1060
|
}
|
|
910
1061
|
};
|
|
911
1062
|
|