@howells/motif-sdk 0.1.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/LICENSE +21 -0
- package/dist/index.cjs +2747 -0
- package/dist/index.d.cts +1128 -0
- package/dist/index.d.ts +1128 -0
- package/dist/index.js +2693 -0
- package/package.json +64 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,2693 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { err as err2, ok as ok2 } from "neverthrow";
|
|
3
|
+
|
|
4
|
+
// src/aspects.ts
|
|
5
|
+
var ASPECT_RATIOS = [
|
|
6
|
+
"auto",
|
|
7
|
+
"1:1",
|
|
8
|
+
"4:3",
|
|
9
|
+
"3:4",
|
|
10
|
+
"16:9",
|
|
11
|
+
"9:16",
|
|
12
|
+
"3:2",
|
|
13
|
+
"2:3",
|
|
14
|
+
"4:5",
|
|
15
|
+
"5:4",
|
|
16
|
+
"21:9",
|
|
17
|
+
"4:1",
|
|
18
|
+
"1:4",
|
|
19
|
+
"8:1",
|
|
20
|
+
"1:8"
|
|
21
|
+
];
|
|
22
|
+
var RESOLUTIONS = ["0.5K", "1K", "2K", "4K"];
|
|
23
|
+
function aspectToGptSize(aspect) {
|
|
24
|
+
switch (aspect) {
|
|
25
|
+
case "auto":
|
|
26
|
+
return "auto";
|
|
27
|
+
case "9:16":
|
|
28
|
+
case "1:8":
|
|
29
|
+
case "1:4":
|
|
30
|
+
case "2:3":
|
|
31
|
+
case "4:5":
|
|
32
|
+
case "3:4":
|
|
33
|
+
return "1024x1536";
|
|
34
|
+
case "16:9":
|
|
35
|
+
case "8:1":
|
|
36
|
+
case "4:1":
|
|
37
|
+
case "3:2":
|
|
38
|
+
case "5:4":
|
|
39
|
+
case "4:3":
|
|
40
|
+
case "21:9":
|
|
41
|
+
return "1536x1024";
|
|
42
|
+
default:
|
|
43
|
+
return "1024x1024";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function aspectToFalImageSize(aspect) {
|
|
47
|
+
switch (aspect) {
|
|
48
|
+
case "auto":
|
|
49
|
+
return "auto";
|
|
50
|
+
case "16:9":
|
|
51
|
+
case "21:9":
|
|
52
|
+
case "4:1":
|
|
53
|
+
case "8:1":
|
|
54
|
+
return "landscape_16_9";
|
|
55
|
+
case "3:2":
|
|
56
|
+
case "5:4":
|
|
57
|
+
return "landscape_4_3";
|
|
58
|
+
case "4:3":
|
|
59
|
+
return "landscape_4_3";
|
|
60
|
+
case "9:16":
|
|
61
|
+
case "1:4":
|
|
62
|
+
case "1:8":
|
|
63
|
+
return "portrait_16_9";
|
|
64
|
+
case "2:3":
|
|
65
|
+
case "4:5":
|
|
66
|
+
return "portrait_4_3";
|
|
67
|
+
case "3:4":
|
|
68
|
+
return "portrait_4_3";
|
|
69
|
+
default:
|
|
70
|
+
return "square_hd";
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
var FORMAT_PRESETS = {
|
|
74
|
+
cover: { label: "Cover", aspect: "2:3", resolution: "2K" },
|
|
75
|
+
square: { label: "Square", aspect: "1:1" },
|
|
76
|
+
landscape: { label: "Landscape", aspect: "16:9" },
|
|
77
|
+
portrait: { label: "Portrait", aspect: "2:3" },
|
|
78
|
+
story: { label: "Story", aspect: "9:16" },
|
|
79
|
+
og: { label: "OG Image", aspect: "16:9" },
|
|
80
|
+
wide: { label: "Wide", aspect: "21:9" }
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// src/models.ts
|
|
84
|
+
var AA_IMAGE_LEADERBOARD_SNAPSHOT = "2026-05-12";
|
|
85
|
+
var AA_IMAGE_SOURCES = [
|
|
86
|
+
"https://artificialanalysis.ai/image/leaderboard/text-to-image",
|
|
87
|
+
"https://artificialanalysis.ai/image/leaderboard/editing"
|
|
88
|
+
];
|
|
89
|
+
var FAL_PRICING_CHECKED_AT = "2026-05-12";
|
|
90
|
+
var MODELS = {
|
|
91
|
+
// ─── Generation Models ────────────────────────────────────────
|
|
92
|
+
gpt2: {
|
|
93
|
+
name: "GPT Image 2",
|
|
94
|
+
endpoint: "openai/gpt-image-2",
|
|
95
|
+
editEndpoint: "openai/gpt-image-2/image-to-image",
|
|
96
|
+
type: "generation",
|
|
97
|
+
pricing: "$0.211",
|
|
98
|
+
pricePerImageUsd: 0.211,
|
|
99
|
+
falPricing: {
|
|
100
|
+
endpointId: "openai/gpt-image-2",
|
|
101
|
+
unitPrice: 1,
|
|
102
|
+
unit: "units",
|
|
103
|
+
currency: "USD",
|
|
104
|
+
estimatedCostPerImageUsd: 0.211,
|
|
105
|
+
source: "fal-pricing-api",
|
|
106
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
107
|
+
},
|
|
108
|
+
benchmark: {
|
|
109
|
+
useCase: "Highest-ranked text-to-image quality and transparent PNGs",
|
|
110
|
+
tiers: { quality: "frontier", speed: "very_slow", price: "ultra" },
|
|
111
|
+
speed: {
|
|
112
|
+
medianSeconds: 200.7,
|
|
113
|
+
p95Seconds: 232.6,
|
|
114
|
+
source: "artificial-analysis-models"
|
|
115
|
+
},
|
|
116
|
+
artificialAnalysis: {
|
|
117
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
118
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
119
|
+
textToImage: { rank: 1, elo: 1337, winRate: 0.8, pricePer1k: 211 },
|
|
120
|
+
editing: { rank: 3, elo: 1249, winRate: 0.66, pricePer1k: 211 }
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
sizeMode: "image_size_enum",
|
|
124
|
+
supportsAspect: true,
|
|
125
|
+
supportsResolution: false,
|
|
126
|
+
supportsEdit: true,
|
|
127
|
+
supportsNumImages: true,
|
|
128
|
+
supportsOutputFormat: true,
|
|
129
|
+
supportsQuality: true,
|
|
130
|
+
supportsMaskImage: true,
|
|
131
|
+
supportsSyncMode: true,
|
|
132
|
+
maxReferenceImages: 4,
|
|
133
|
+
useQueue: true
|
|
134
|
+
},
|
|
135
|
+
gpt: {
|
|
136
|
+
name: "GPT Image 1.5",
|
|
137
|
+
endpoint: "fal-ai/gpt-image-1.5",
|
|
138
|
+
editEndpoint: "fal-ai/gpt-image-1.5/edit",
|
|
139
|
+
type: "generation",
|
|
140
|
+
pricing: "$0.133",
|
|
141
|
+
pricePerImageUsd: 0.133,
|
|
142
|
+
falPricing: {
|
|
143
|
+
endpointId: "fal-ai/gpt-image-1.5",
|
|
144
|
+
unitPrice: 1,
|
|
145
|
+
unit: "units",
|
|
146
|
+
currency: "USD",
|
|
147
|
+
estimatedCostPerImageUsd: 0.133,
|
|
148
|
+
source: "fal-pricing-api",
|
|
149
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
150
|
+
},
|
|
151
|
+
benchmark: {
|
|
152
|
+
useCase: "Best OpenAI edit quality with transparent PNG support",
|
|
153
|
+
tiers: { quality: "frontier", speed: "slow", price: "ultra" },
|
|
154
|
+
speed: {
|
|
155
|
+
medianSeconds: 34.6,
|
|
156
|
+
p95Seconds: 38.3,
|
|
157
|
+
source: "artificial-analysis-models"
|
|
158
|
+
},
|
|
159
|
+
artificialAnalysis: {
|
|
160
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
161
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
162
|
+
textToImage: { rank: 2, elo: 1268, winRate: 0.73, pricePer1k: 133 },
|
|
163
|
+
editing: { rank: 2, elo: 1262, winRate: 0.69, pricePer1k: 133 }
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
sizeMode: "gpt_size",
|
|
167
|
+
supportsAspect: false,
|
|
168
|
+
supportsBackground: true,
|
|
169
|
+
supportsResolution: false,
|
|
170
|
+
supportsEdit: true,
|
|
171
|
+
supportsNumImages: true,
|
|
172
|
+
supportsOutputFormat: true,
|
|
173
|
+
supportsQuality: true,
|
|
174
|
+
supportsMaskImage: true,
|
|
175
|
+
supportsSyncMode: true,
|
|
176
|
+
maxReferenceImages: 4
|
|
177
|
+
},
|
|
178
|
+
banana2: {
|
|
179
|
+
name: "Nano Banana 2",
|
|
180
|
+
endpoint: "fal-ai/nano-banana-2",
|
|
181
|
+
editEndpoint: "fal-ai/nano-banana-2/edit",
|
|
182
|
+
type: "generation",
|
|
183
|
+
pricing: "$0.08",
|
|
184
|
+
pricePerImageUsd: 0.08,
|
|
185
|
+
falPricing: {
|
|
186
|
+
endpointId: "fal-ai/nano-banana-2",
|
|
187
|
+
unitPrice: 0.08,
|
|
188
|
+
unit: "images",
|
|
189
|
+
currency: "USD",
|
|
190
|
+
estimatedCostPerImageUsd: 0.08,
|
|
191
|
+
source: "fal-pricing-api",
|
|
192
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
193
|
+
},
|
|
194
|
+
benchmark: {
|
|
195
|
+
useCase: "Best balance of quality, edit support, web search, and cost",
|
|
196
|
+
tiers: { quality: "frontier", speed: "unknown", price: "premium" },
|
|
197
|
+
artificialAnalysis: {
|
|
198
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
199
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
200
|
+
textToImage: { rank: 3, elo: 1263, winRate: 0.71, pricePer1k: 67 },
|
|
201
|
+
editing: { rank: 5, elo: 1231, winRate: 0.63, pricePer1k: 67 }
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
sizeMode: "aspect_ratio",
|
|
205
|
+
supportsAspect: true,
|
|
206
|
+
supportsResolution: true,
|
|
207
|
+
supportsEdit: true,
|
|
208
|
+
supportsNumImages: true,
|
|
209
|
+
supportsOutputFormat: true,
|
|
210
|
+
supportsSafetyTolerance: true,
|
|
211
|
+
supportsSeed: true,
|
|
212
|
+
supportsWebSearch: true,
|
|
213
|
+
supportsGoogleSearch: true,
|
|
214
|
+
supportsLimitGenerations: true,
|
|
215
|
+
supportsSyncMode: true,
|
|
216
|
+
supportsThinkingLevel: true,
|
|
217
|
+
maxReferenceImages: 4
|
|
218
|
+
},
|
|
219
|
+
banana: {
|
|
220
|
+
name: "Nano Banana Pro",
|
|
221
|
+
endpoint: "fal-ai/nano-banana-pro",
|
|
222
|
+
editEndpoint: "fal-ai/nano-banana-pro/edit",
|
|
223
|
+
type: "generation",
|
|
224
|
+
pricing: "$0.15",
|
|
225
|
+
pricePerImageUsd: 0.15,
|
|
226
|
+
falPricing: {
|
|
227
|
+
endpointId: "fal-ai/nano-banana-pro",
|
|
228
|
+
unitPrice: 0.15,
|
|
229
|
+
unit: "images",
|
|
230
|
+
currency: "USD",
|
|
231
|
+
estimatedCostPerImageUsd: 0.15,
|
|
232
|
+
source: "fal-pricing-api",
|
|
233
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
234
|
+
},
|
|
235
|
+
benchmark: {
|
|
236
|
+
useCase: "Premium Gemini image generation and multi-reference edits",
|
|
237
|
+
tiers: { quality: "frontier", speed: "balanced", price: "ultra" },
|
|
238
|
+
speed: {
|
|
239
|
+
medianSeconds: 19.2,
|
|
240
|
+
p95Seconds: 30.3,
|
|
241
|
+
source: "artificial-analysis-models"
|
|
242
|
+
},
|
|
243
|
+
artificialAnalysis: {
|
|
244
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
245
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
246
|
+
textToImage: { rank: 5, elo: 1220, winRate: 0.66, pricePer1k: 134 },
|
|
247
|
+
editing: { rank: 4, elo: 1241, winRate: 0.65, pricePer1k: 134 }
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
sizeMode: "aspect_ratio",
|
|
251
|
+
supportsAspect: true,
|
|
252
|
+
supportsResolution: true,
|
|
253
|
+
supportsEdit: true,
|
|
254
|
+
supportsNumImages: true,
|
|
255
|
+
supportsOutputFormat: true,
|
|
256
|
+
supportsSafetyTolerance: true,
|
|
257
|
+
supportsSeed: true,
|
|
258
|
+
supportsWebSearch: true,
|
|
259
|
+
supportsGoogleSearch: true,
|
|
260
|
+
supportsLimitGenerations: true,
|
|
261
|
+
supportsSyncMode: true,
|
|
262
|
+
maxReferenceImages: 14
|
|
263
|
+
},
|
|
264
|
+
gemini: {
|
|
265
|
+
name: "Gemini 2.5 Flash",
|
|
266
|
+
endpoint: "fal-ai/gemini-25-flash-image",
|
|
267
|
+
editEndpoint: "fal-ai/gemini-25-flash-image/edit",
|
|
268
|
+
type: "generation",
|
|
269
|
+
pricing: "$0.04",
|
|
270
|
+
pricePerImageUsd: 0.0398,
|
|
271
|
+
falPricing: {
|
|
272
|
+
endpointId: "fal-ai/gemini-25-flash-image",
|
|
273
|
+
unitPrice: 0.0398,
|
|
274
|
+
unit: "images",
|
|
275
|
+
currency: "USD",
|
|
276
|
+
estimatedCostPerImageUsd: 0.0398,
|
|
277
|
+
source: "fal-pricing-api",
|
|
278
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
279
|
+
},
|
|
280
|
+
sizeMode: "aspect_ratio",
|
|
281
|
+
supportsAspect: true,
|
|
282
|
+
supportsResolution: false,
|
|
283
|
+
supportsEdit: true,
|
|
284
|
+
supportsNumImages: true,
|
|
285
|
+
supportsOutputFormat: true,
|
|
286
|
+
supportsSafetyTolerance: true,
|
|
287
|
+
supportsSeed: true,
|
|
288
|
+
supportsSyncMode: true,
|
|
289
|
+
maxReferenceImages: 4
|
|
290
|
+
},
|
|
291
|
+
gemini3: {
|
|
292
|
+
name: "Gemini 3 Pro",
|
|
293
|
+
endpoint: "fal-ai/gemini-3-pro-image-preview",
|
|
294
|
+
editEndpoint: "fal-ai/gemini-3-pro-image-preview/edit",
|
|
295
|
+
type: "generation",
|
|
296
|
+
pricing: "$0.15",
|
|
297
|
+
pricePerImageUsd: 0.15,
|
|
298
|
+
falPricing: {
|
|
299
|
+
endpointId: "fal-ai/gemini-3-pro-image-preview",
|
|
300
|
+
unitPrice: 0.15,
|
|
301
|
+
unit: "images",
|
|
302
|
+
currency: "USD",
|
|
303
|
+
estimatedCostPerImageUsd: 0.15,
|
|
304
|
+
source: "fal-pricing-api",
|
|
305
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
306
|
+
},
|
|
307
|
+
sizeMode: "aspect_ratio",
|
|
308
|
+
supportsAspect: true,
|
|
309
|
+
supportsResolution: true,
|
|
310
|
+
supportsEdit: true,
|
|
311
|
+
supportsNumImages: true,
|
|
312
|
+
supportsOutputFormat: true,
|
|
313
|
+
supportsSafetyTolerance: true,
|
|
314
|
+
supportsSeed: true,
|
|
315
|
+
supportsWebSearch: true,
|
|
316
|
+
supportsSyncMode: true,
|
|
317
|
+
maxReferenceImages: 4
|
|
318
|
+
},
|
|
319
|
+
seedream4: {
|
|
320
|
+
name: "Seedream 4.0",
|
|
321
|
+
endpoint: "fal-ai/bytedance/seedream/v4/text-to-image",
|
|
322
|
+
editEndpoint: "fal-ai/bytedance/seedream/v4/edit",
|
|
323
|
+
type: "generation",
|
|
324
|
+
pricing: "$0.03",
|
|
325
|
+
pricePerImageUsd: 0.03,
|
|
326
|
+
falPricing: {
|
|
327
|
+
endpointId: "fal-ai/bytedance/seedream/v4/text-to-image",
|
|
328
|
+
unitPrice: 0.03,
|
|
329
|
+
unit: "images",
|
|
330
|
+
currency: "USD",
|
|
331
|
+
estimatedCostPerImageUsd: 0.03,
|
|
332
|
+
source: "fal-pricing-api",
|
|
333
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
334
|
+
},
|
|
335
|
+
benchmark: {
|
|
336
|
+
useCase: "High-ranked budget generation and large multi-reference edits",
|
|
337
|
+
tiers: { quality: "best", speed: "balanced", price: "budget" },
|
|
338
|
+
speed: {
|
|
339
|
+
medianSeconds: 14.3,
|
|
340
|
+
p95Seconds: 21,
|
|
341
|
+
source: "artificial-analysis-models"
|
|
342
|
+
},
|
|
343
|
+
artificialAnalysis: {
|
|
344
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
345
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
346
|
+
textToImage: { rank: 6, elo: 1198, winRate: 0.59, pricePer1k: 30 },
|
|
347
|
+
editing: { rank: 16, elo: 1184, winRate: 0.61, pricePer1k: 30 }
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
sizeMode: "image_size_enum",
|
|
351
|
+
supportsAspect: true,
|
|
352
|
+
supportsResolution: false,
|
|
353
|
+
supportsEdit: true,
|
|
354
|
+
supportsNumImages: true,
|
|
355
|
+
supportsSeed: true,
|
|
356
|
+
supportsSyncMode: true,
|
|
357
|
+
maxReferenceImages: 10
|
|
358
|
+
},
|
|
359
|
+
seedream45: {
|
|
360
|
+
name: "Seedream 4.5",
|
|
361
|
+
endpoint: "fal-ai/bytedance/seedream/v4.5/text-to-image",
|
|
362
|
+
editEndpoint: "fal-ai/bytedance/seedream/v4.5/edit",
|
|
363
|
+
type: "generation",
|
|
364
|
+
pricing: "$0.04",
|
|
365
|
+
pricePerImageUsd: 0.04,
|
|
366
|
+
falPricing: {
|
|
367
|
+
endpointId: "fal-ai/bytedance/seedream/v4.5/text-to-image",
|
|
368
|
+
unitPrice: 0.04,
|
|
369
|
+
unit: "images",
|
|
370
|
+
currency: "USD",
|
|
371
|
+
estimatedCostPerImageUsd: 0.04,
|
|
372
|
+
source: "fal-pricing-api",
|
|
373
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
374
|
+
},
|
|
375
|
+
benchmark: {
|
|
376
|
+
useCase: "Cheap current Seedream model with good animation/art scores",
|
|
377
|
+
tiers: { quality: "better", speed: "balanced", price: "standard" },
|
|
378
|
+
speed: {
|
|
379
|
+
medianSeconds: 18.2,
|
|
380
|
+
p95Seconds: 43.3,
|
|
381
|
+
source: "artificial-analysis-models"
|
|
382
|
+
},
|
|
383
|
+
artificialAnalysis: {
|
|
384
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
385
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
386
|
+
textToImage: { rank: 17, elo: 1167, winRate: 0.63, pricePer1k: 40 },
|
|
387
|
+
editing: { rank: 17, elo: 1184, winRate: 0.58, pricePer1k: 40 }
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
sizeMode: "image_size_enum",
|
|
391
|
+
supportsAspect: true,
|
|
392
|
+
supportsResolution: false,
|
|
393
|
+
supportsEdit: true,
|
|
394
|
+
supportsNumImages: true,
|
|
395
|
+
supportsSeed: true,
|
|
396
|
+
supportsSyncMode: true,
|
|
397
|
+
maxReferenceImages: 10
|
|
398
|
+
},
|
|
399
|
+
"flux2-max": {
|
|
400
|
+
name: "FLUX.2 Max",
|
|
401
|
+
endpoint: "fal-ai/flux-2-max",
|
|
402
|
+
editEndpoint: "fal-ai/flux-2-max/edit",
|
|
403
|
+
type: "generation",
|
|
404
|
+
pricing: "$0.07/MP",
|
|
405
|
+
pricePerImageUsd: 0.07,
|
|
406
|
+
falPricing: {
|
|
407
|
+
endpointId: "fal-ai/flux-2-max",
|
|
408
|
+
unitPrice: 0.07,
|
|
409
|
+
unit: "megapixels",
|
|
410
|
+
currency: "USD",
|
|
411
|
+
estimatedCostPerImageUsd: 0.07,
|
|
412
|
+
source: "fal-pricing-api",
|
|
413
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
414
|
+
},
|
|
415
|
+
benchmark: {
|
|
416
|
+
useCase: "Best FLUX quality and high-end editing",
|
|
417
|
+
tiers: { quality: "best", speed: "slow", price: "premium" },
|
|
418
|
+
speed: {
|
|
419
|
+
medianSeconds: 31.6,
|
|
420
|
+
p95Seconds: 41,
|
|
421
|
+
source: "artificial-analysis-models"
|
|
422
|
+
},
|
|
423
|
+
artificialAnalysis: {
|
|
424
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
425
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
426
|
+
textToImage: { rank: 8, elo: 1197, winRate: 0.67, pricePer1k: 70 },
|
|
427
|
+
editing: { rank: 10, elo: 1206, winRate: 0.6, pricePer1k: 140 }
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
sizeMode: "image_size_enum",
|
|
431
|
+
supportsAspect: true,
|
|
432
|
+
supportsResolution: false,
|
|
433
|
+
supportsEdit: true,
|
|
434
|
+
supportsNumImages: false,
|
|
435
|
+
supportsOutputFormat: true,
|
|
436
|
+
supportsSafetyTolerance: true,
|
|
437
|
+
supportsSafetyChecker: true,
|
|
438
|
+
supportsSeed: true,
|
|
439
|
+
supportsSyncMode: true,
|
|
440
|
+
maxReferenceImages: 10
|
|
441
|
+
},
|
|
442
|
+
"flux2-pro": {
|
|
443
|
+
name: "FLUX.2 Pro",
|
|
444
|
+
endpoint: "fal-ai/flux-2-pro",
|
|
445
|
+
editEndpoint: "fal-ai/flux-2-pro/edit",
|
|
446
|
+
type: "generation",
|
|
447
|
+
pricing: "$0.03/MP",
|
|
448
|
+
pricePerImageUsd: 0.03,
|
|
449
|
+
falPricing: {
|
|
450
|
+
endpointId: "fal-ai/flux-2-pro",
|
|
451
|
+
unitPrice: 0.03,
|
|
452
|
+
unit: "processed megapixels",
|
|
453
|
+
currency: "USD",
|
|
454
|
+
estimatedCostPerImageUsd: 0.03,
|
|
455
|
+
source: "fal-pricing-api",
|
|
456
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
457
|
+
},
|
|
458
|
+
benchmark: {
|
|
459
|
+
useCase: "Production FLUX quality at a low per-megapixel price",
|
|
460
|
+
tiers: { quality: "best", speed: "balanced", price: "budget" },
|
|
461
|
+
speed: {
|
|
462
|
+
medianSeconds: 16.1,
|
|
463
|
+
p95Seconds: 23.9,
|
|
464
|
+
source: "artificial-analysis-models"
|
|
465
|
+
},
|
|
466
|
+
artificialAnalysis: {
|
|
467
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
468
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
469
|
+
textToImage: { rank: 10, elo: 1186, winRate: 0.62, pricePer1k: 30 },
|
|
470
|
+
editing: { rank: 21, elo: 1170, pricePer1k: 30 }
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
sizeMode: "image_size_enum",
|
|
474
|
+
supportsAspect: true,
|
|
475
|
+
supportsResolution: false,
|
|
476
|
+
supportsEdit: true,
|
|
477
|
+
supportsNumImages: false,
|
|
478
|
+
supportsOutputFormat: true,
|
|
479
|
+
supportsSafetyTolerance: true,
|
|
480
|
+
supportsSafetyChecker: true,
|
|
481
|
+
supportsSeed: true,
|
|
482
|
+
supportsSyncMode: true,
|
|
483
|
+
maxReferenceImages: 10
|
|
484
|
+
},
|
|
485
|
+
"flux2-flex": {
|
|
486
|
+
name: "FLUX.2 Flex",
|
|
487
|
+
endpoint: "fal-ai/flux-2-flex",
|
|
488
|
+
editEndpoint: "fal-ai/flux-2-flex/edit",
|
|
489
|
+
type: "generation",
|
|
490
|
+
pricing: "$0.05/MP",
|
|
491
|
+
pricePerImageUsd: 0.05,
|
|
492
|
+
falPricing: {
|
|
493
|
+
endpointId: "fal-ai/flux-2-flex",
|
|
494
|
+
unitPrice: 0.05,
|
|
495
|
+
unit: "processed megapixels",
|
|
496
|
+
currency: "USD",
|
|
497
|
+
estimatedCostPerImageUsd: 0.05,
|
|
498
|
+
source: "fal-pricing-api",
|
|
499
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
500
|
+
},
|
|
501
|
+
benchmark: {
|
|
502
|
+
useCase: "FLUX quality with guidance and step controls",
|
|
503
|
+
tiers: { quality: "best", speed: "balanced", price: "standard" },
|
|
504
|
+
speed: {
|
|
505
|
+
medianSeconds: 15.3,
|
|
506
|
+
p95Seconds: 22.2,
|
|
507
|
+
source: "artificial-analysis-models"
|
|
508
|
+
},
|
|
509
|
+
artificialAnalysis: {
|
|
510
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
511
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
512
|
+
textToImage: { rank: 13, elo: 1182, winRate: 0.61, pricePer1k: 60 },
|
|
513
|
+
editing: { rank: 22, elo: 1161, pricePer1k: 60 }
|
|
514
|
+
}
|
|
515
|
+
},
|
|
516
|
+
sizeMode: "image_size_enum",
|
|
517
|
+
supportsAspect: true,
|
|
518
|
+
supportsResolution: false,
|
|
519
|
+
supportsEdit: true,
|
|
520
|
+
supportsNumImages: false,
|
|
521
|
+
supportsOutputFormat: true,
|
|
522
|
+
supportedOutputFormats: ["jpeg", "png"],
|
|
523
|
+
supportsSafetyTolerance: true,
|
|
524
|
+
supportsSafetyChecker: true,
|
|
525
|
+
supportsSeed: true,
|
|
526
|
+
supportsGuidanceScale: true,
|
|
527
|
+
supportsInferenceSteps: true,
|
|
528
|
+
supportsSyncMode: true,
|
|
529
|
+
maxReferenceImages: 10
|
|
530
|
+
},
|
|
531
|
+
"flux2-dev": {
|
|
532
|
+
name: "FLUX.2 Dev",
|
|
533
|
+
endpoint: "fal-ai/flux-2",
|
|
534
|
+
editEndpoint: "fal-ai/flux-2/edit",
|
|
535
|
+
type: "generation",
|
|
536
|
+
pricing: "$0.00167/sec",
|
|
537
|
+
pricePerImageUsd: 0.012,
|
|
538
|
+
falPricing: {
|
|
539
|
+
endpointId: "fal-ai/flux-2",
|
|
540
|
+
unitPrice: 167e-5,
|
|
541
|
+
unit: "compute seconds",
|
|
542
|
+
currency: "USD",
|
|
543
|
+
estimatedCostPerImageUsd: 0.012,
|
|
544
|
+
source: "fal-pricing-api",
|
|
545
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
546
|
+
},
|
|
547
|
+
benchmark: {
|
|
548
|
+
useCase: "Open FLUX.2 quality with low average cost",
|
|
549
|
+
tiers: { quality: "better", speed: "fast", price: "variable" },
|
|
550
|
+
speed: {
|
|
551
|
+
medianSeconds: 4.6,
|
|
552
|
+
p95Seconds: 69.6,
|
|
553
|
+
source: "artificial-analysis-models"
|
|
554
|
+
},
|
|
555
|
+
artificialAnalysis: {
|
|
556
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
557
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
558
|
+
textToImage: { rank: 19, elo: 1160, winRate: 0.57, pricePer1k: 12 }
|
|
559
|
+
}
|
|
560
|
+
},
|
|
561
|
+
sizeMode: "image_size_enum",
|
|
562
|
+
supportsAspect: true,
|
|
563
|
+
supportsResolution: false,
|
|
564
|
+
supportsEdit: true,
|
|
565
|
+
supportsNumImages: true,
|
|
566
|
+
supportsOutputFormat: true,
|
|
567
|
+
supportsSafetyTolerance: true,
|
|
568
|
+
supportsSafetyChecker: true,
|
|
569
|
+
supportsSeed: true,
|
|
570
|
+
supportsGuidanceScale: true,
|
|
571
|
+
supportsInferenceSteps: true,
|
|
572
|
+
supportsSyncMode: true,
|
|
573
|
+
maxReferenceImages: 10
|
|
574
|
+
},
|
|
575
|
+
flux: {
|
|
576
|
+
name: "FLUX Pro Ultra",
|
|
577
|
+
endpoint: "fal-ai/flux-pro/v1.1-ultra",
|
|
578
|
+
type: "generation",
|
|
579
|
+
pricing: "$0.06",
|
|
580
|
+
pricePerImageUsd: 0.06,
|
|
581
|
+
falPricing: {
|
|
582
|
+
endpointId: "fal-ai/flux-pro/v1.1-ultra",
|
|
583
|
+
unitPrice: 0.06,
|
|
584
|
+
unit: "images",
|
|
585
|
+
currency: "USD",
|
|
586
|
+
estimatedCostPerImageUsd: 0.06,
|
|
587
|
+
source: "fal-pricing-api",
|
|
588
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
589
|
+
},
|
|
590
|
+
sizeMode: "aspect_ratio",
|
|
591
|
+
supportsAspect: true,
|
|
592
|
+
supportsResolution: false,
|
|
593
|
+
supportsEdit: true,
|
|
594
|
+
supportsNumImages: true,
|
|
595
|
+
supportsOutputFormat: true,
|
|
596
|
+
supportsRaw: true,
|
|
597
|
+
supportsEnhancePrompt: true,
|
|
598
|
+
supportsSafetyTolerance: true,
|
|
599
|
+
supportsSeed: true,
|
|
600
|
+
supportsImagePromptStrength: true,
|
|
601
|
+
supportsSyncMode: true,
|
|
602
|
+
maxReferenceImages: 1
|
|
603
|
+
},
|
|
604
|
+
"flux-fast": {
|
|
605
|
+
name: "FLUX Schnell",
|
|
606
|
+
endpoint: "fal-ai/flux/schnell",
|
|
607
|
+
type: "generation",
|
|
608
|
+
pricing: "$0.003",
|
|
609
|
+
pricePerImageUsd: 3e-3,
|
|
610
|
+
falPricing: {
|
|
611
|
+
endpointId: "fal-ai/flux/schnell",
|
|
612
|
+
unitPrice: 3e-3,
|
|
613
|
+
unit: "megapixels",
|
|
614
|
+
currency: "USD",
|
|
615
|
+
estimatedCostPerImageUsd: 3e-3,
|
|
616
|
+
source: "fal-pricing-api",
|
|
617
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
618
|
+
},
|
|
619
|
+
sizeMode: "image_size_enum",
|
|
620
|
+
supportsAspect: true,
|
|
621
|
+
supportsResolution: false,
|
|
622
|
+
supportsEdit: false,
|
|
623
|
+
supportsNumImages: true,
|
|
624
|
+
supportsOutputFormat: true,
|
|
625
|
+
supportsGuidanceScale: true,
|
|
626
|
+
supportsInferenceSteps: true,
|
|
627
|
+
supportsSeed: true,
|
|
628
|
+
supportsSyncMode: true
|
|
629
|
+
},
|
|
630
|
+
recraft: {
|
|
631
|
+
name: "Recraft V3",
|
|
632
|
+
endpoint: "fal-ai/recraft-v3",
|
|
633
|
+
type: "generation",
|
|
634
|
+
pricing: "$0.04",
|
|
635
|
+
pricePerImageUsd: 0.04,
|
|
636
|
+
falPricing: {
|
|
637
|
+
endpointId: "fal-ai/recraft-v3",
|
|
638
|
+
unitPrice: 0.04,
|
|
639
|
+
unit: "images",
|
|
640
|
+
currency: "USD",
|
|
641
|
+
estimatedCostPerImageUsd: 0.04,
|
|
642
|
+
source: "fal-pricing-api",
|
|
643
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
644
|
+
},
|
|
645
|
+
sizeMode: "image_size_enum",
|
|
646
|
+
supportsAspect: true,
|
|
647
|
+
supportsResolution: false,
|
|
648
|
+
supportsEdit: false,
|
|
649
|
+
supportsNumImages: false,
|
|
650
|
+
supportsStyle: true,
|
|
651
|
+
supportsSyncMode: true
|
|
652
|
+
},
|
|
653
|
+
ideogram: {
|
|
654
|
+
name: "Ideogram V3",
|
|
655
|
+
endpoint: "fal-ai/ideogram/v3",
|
|
656
|
+
type: "generation",
|
|
657
|
+
pricing: "$0.03",
|
|
658
|
+
pricePerImageUsd: 0.03,
|
|
659
|
+
falPricing: {
|
|
660
|
+
endpointId: "fal-ai/ideogram/v3",
|
|
661
|
+
unitPrice: 0.03,
|
|
662
|
+
unit: "images",
|
|
663
|
+
currency: "USD",
|
|
664
|
+
estimatedCostPerImageUsd: 0.03,
|
|
665
|
+
source: "fal-pricing-api",
|
|
666
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
667
|
+
},
|
|
668
|
+
sizeMode: "image_size_enum",
|
|
669
|
+
supportsAspect: true,
|
|
670
|
+
supportsResolution: false,
|
|
671
|
+
supportsEdit: false,
|
|
672
|
+
supportsNumImages: true,
|
|
673
|
+
supportsExpandPrompt: true,
|
|
674
|
+
supportsNegativePrompt: true,
|
|
675
|
+
supportsRenderingSpeed: true,
|
|
676
|
+
supportsSeed: true,
|
|
677
|
+
supportsStyle: true,
|
|
678
|
+
supportsSyncMode: true
|
|
679
|
+
},
|
|
680
|
+
"grok-image": {
|
|
681
|
+
name: "Grok Imagine Image",
|
|
682
|
+
endpoint: "xai/grok-imagine-image",
|
|
683
|
+
editEndpoint: "xai/grok-imagine-image/edit",
|
|
684
|
+
type: "generation",
|
|
685
|
+
pricing: "$0.02",
|
|
686
|
+
pricePerImageUsd: 0.02,
|
|
687
|
+
falPricing: {
|
|
688
|
+
endpointId: "xai/grok-imagine-image",
|
|
689
|
+
unitPrice: 0.02,
|
|
690
|
+
unit: "images",
|
|
691
|
+
currency: "USD",
|
|
692
|
+
estimatedCostPerImageUsd: 0.02,
|
|
693
|
+
source: "fal-pricing-api",
|
|
694
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
695
|
+
},
|
|
696
|
+
benchmark: {
|
|
697
|
+
useCase: "Very fast, cheap generation and edits",
|
|
698
|
+
tiers: { quality: "best", speed: "fast", price: "budget" },
|
|
699
|
+
speed: {
|
|
700
|
+
medianSeconds: 5.1,
|
|
701
|
+
p95Seconds: 6.5,
|
|
702
|
+
source: "artificial-analysis-models"
|
|
703
|
+
},
|
|
704
|
+
artificialAnalysis: {
|
|
705
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
706
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
707
|
+
textToImage: { rank: 12, elo: 1182, winRate: 0.6, pricePer1k: 20 },
|
|
708
|
+
editing: { rank: 7, elo: 1213, winRate: 0.6, pricePer1k: 20 }
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
sizeMode: "aspect_ratio",
|
|
712
|
+
supportsAspect: true,
|
|
713
|
+
supportsResolution: true,
|
|
714
|
+
supportsEdit: true,
|
|
715
|
+
supportsNumImages: true,
|
|
716
|
+
supportsOutputFormat: true,
|
|
717
|
+
supportsSyncMode: true,
|
|
718
|
+
maxReferenceImages: 4
|
|
719
|
+
},
|
|
720
|
+
qwen: {
|
|
721
|
+
name: "Qwen Image",
|
|
722
|
+
endpoint: "fal-ai/qwen-image",
|
|
723
|
+
type: "generation",
|
|
724
|
+
pricing: "$0.02/MP",
|
|
725
|
+
pricePerImageUsd: 0.02,
|
|
726
|
+
falPricing: {
|
|
727
|
+
endpointId: "fal-ai/qwen-image",
|
|
728
|
+
unitPrice: 0.02,
|
|
729
|
+
unit: "megapixels",
|
|
730
|
+
currency: "USD",
|
|
731
|
+
estimatedCostPerImageUsd: 0.02,
|
|
732
|
+
source: "fal-pricing-api",
|
|
733
|
+
checkedAt: FAL_PRICING_CHECKED_AT
|
|
734
|
+
},
|
|
735
|
+
benchmark: {
|
|
736
|
+
useCase: "Low-cost open-weight text and design generation",
|
|
737
|
+
tiers: { quality: "better", speed: "slow", price: "budget" },
|
|
738
|
+
speed: {
|
|
739
|
+
medianSeconds: 23.3,
|
|
740
|
+
p95Seconds: 491,
|
|
741
|
+
source: "artificial-analysis-models"
|
|
742
|
+
},
|
|
743
|
+
artificialAnalysis: {
|
|
744
|
+
snapshotDate: AA_IMAGE_LEADERBOARD_SNAPSHOT,
|
|
745
|
+
sourceUrls: AA_IMAGE_SOURCES,
|
|
746
|
+
textToImage: { rank: 20, elo: 1158, winRate: 0.6, pricePer1k: 20 }
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
sizeMode: "image_size_enum",
|
|
750
|
+
supportsAspect: true,
|
|
751
|
+
supportsResolution: false,
|
|
752
|
+
supportsEdit: false,
|
|
753
|
+
supportsNumImages: true,
|
|
754
|
+
supportsOutputFormat: true,
|
|
755
|
+
supportsSeed: true,
|
|
756
|
+
supportsSyncMode: true
|
|
757
|
+
},
|
|
758
|
+
// ─── Video Models ─────────────────────────────────────────────
|
|
759
|
+
kling: {
|
|
760
|
+
name: "Kling v3 Pro",
|
|
761
|
+
endpoint: "fal-ai/kling-video/v3/pro/image-to-video",
|
|
762
|
+
type: "video",
|
|
763
|
+
pricing: "$0.11/sec",
|
|
764
|
+
sizeMode: "none",
|
|
765
|
+
supportsAspect: false,
|
|
766
|
+
supportsResolution: false,
|
|
767
|
+
supportsEdit: false,
|
|
768
|
+
supportsNumImages: false
|
|
769
|
+
},
|
|
770
|
+
// ─── Utility Models ───────────────────────────────────────────
|
|
771
|
+
clarity: {
|
|
772
|
+
name: "Clarity Upscaler",
|
|
773
|
+
endpoint: "fal-ai/clarity-upscaler",
|
|
774
|
+
type: "utility",
|
|
775
|
+
pricing: "$0.02",
|
|
776
|
+
supportsAspect: false,
|
|
777
|
+
supportsResolution: false,
|
|
778
|
+
supportsEdit: false,
|
|
779
|
+
supportsNumImages: false
|
|
780
|
+
},
|
|
781
|
+
crystal: {
|
|
782
|
+
name: "Crystal Upscaler",
|
|
783
|
+
endpoint: "clarityai/crystal-upscaler",
|
|
784
|
+
type: "utility",
|
|
785
|
+
pricing: "$0.02",
|
|
786
|
+
supportsAspect: false,
|
|
787
|
+
supportsResolution: false,
|
|
788
|
+
supportsEdit: false,
|
|
789
|
+
supportsNumImages: false
|
|
790
|
+
},
|
|
791
|
+
rmbg: {
|
|
792
|
+
name: "BiRefNet (Background Removal)",
|
|
793
|
+
endpoint: "fal-ai/birefnet",
|
|
794
|
+
type: "utility",
|
|
795
|
+
pricing: "$0.02",
|
|
796
|
+
supportsAspect: false,
|
|
797
|
+
supportsResolution: false,
|
|
798
|
+
supportsEdit: false,
|
|
799
|
+
supportsNumImages: false
|
|
800
|
+
},
|
|
801
|
+
bria: {
|
|
802
|
+
name: "Bria RMBG 2.0",
|
|
803
|
+
endpoint: "fal-ai/bria/background/remove",
|
|
804
|
+
type: "utility",
|
|
805
|
+
pricing: "$0.02",
|
|
806
|
+
supportsAspect: false,
|
|
807
|
+
supportsResolution: false,
|
|
808
|
+
supportsEdit: false,
|
|
809
|
+
supportsNumImages: false
|
|
810
|
+
}
|
|
811
|
+
};
|
|
812
|
+
var GENERATION_MODELS = [
|
|
813
|
+
"gpt2",
|
|
814
|
+
"gpt",
|
|
815
|
+
"banana2",
|
|
816
|
+
"banana",
|
|
817
|
+
"gemini",
|
|
818
|
+
"gemini3",
|
|
819
|
+
"seedream4",
|
|
820
|
+
"seedream45",
|
|
821
|
+
"flux2-max",
|
|
822
|
+
"flux2-pro",
|
|
823
|
+
"flux2-flex",
|
|
824
|
+
"flux2-dev",
|
|
825
|
+
"flux",
|
|
826
|
+
"flux-fast",
|
|
827
|
+
"recraft",
|
|
828
|
+
"ideogram",
|
|
829
|
+
"grok-image",
|
|
830
|
+
"qwen"
|
|
831
|
+
];
|
|
832
|
+
var UTILITY_MODELS = ["clarity", "crystal", "rmbg", "bria"];
|
|
833
|
+
var VIDEO_MODELS = ["kling"];
|
|
834
|
+
var RECRAFT_STYLES = [
|
|
835
|
+
// Realistic
|
|
836
|
+
"realistic_image",
|
|
837
|
+
"realistic_image/b_and_w",
|
|
838
|
+
"realistic_image/enterprise",
|
|
839
|
+
"realistic_image/hard_flash",
|
|
840
|
+
"realistic_image/hdr",
|
|
841
|
+
"realistic_image/motion_blur",
|
|
842
|
+
"realistic_image/natural_light",
|
|
843
|
+
"realistic_image/studio_portrait",
|
|
844
|
+
"realistic_image/long_exposure",
|
|
845
|
+
// Digital illustration
|
|
846
|
+
"digital_illustration",
|
|
847
|
+
"digital_illustration/2d_art_poster",
|
|
848
|
+
"digital_illustration/2d_art_poster_2",
|
|
849
|
+
"digital_illustration/engraving_color",
|
|
850
|
+
"digital_illustration/grain",
|
|
851
|
+
"digital_illustration/hand_drawn",
|
|
852
|
+
"digital_illustration/hand_drawn_outline",
|
|
853
|
+
"digital_illustration/handmade_3d",
|
|
854
|
+
"digital_illustration/infantile_sketch",
|
|
855
|
+
"digital_illustration/pixel_art",
|
|
856
|
+
"digital_illustration/psychedelic",
|
|
857
|
+
"digital_illustration/seamless",
|
|
858
|
+
"digital_illustration/voxel",
|
|
859
|
+
"digital_illustration/watercolor",
|
|
860
|
+
// Vector illustration
|
|
861
|
+
"vector_illustration",
|
|
862
|
+
"vector_illustration/cartoon",
|
|
863
|
+
"vector_illustration/doodle_line_art",
|
|
864
|
+
"vector_illustration/engraving",
|
|
865
|
+
"vector_illustration/flat_2",
|
|
866
|
+
"vector_illustration/kawaii",
|
|
867
|
+
"vector_illustration/line_art",
|
|
868
|
+
"vector_illustration/line_circuit",
|
|
869
|
+
"vector_illustration/linocut",
|
|
870
|
+
"vector_illustration/zentangle"
|
|
871
|
+
];
|
|
872
|
+
var IDEOGRAM_STYLES = [
|
|
873
|
+
"AUTO",
|
|
874
|
+
"GENERAL",
|
|
875
|
+
"REALISTIC",
|
|
876
|
+
"DESIGN"
|
|
877
|
+
];
|
|
878
|
+
|
|
879
|
+
// src/cost.ts
|
|
880
|
+
function estimateCost(model, resolution, numImages = 1) {
|
|
881
|
+
const configuredPrice = MODELS[model]?.pricePerImageUsd;
|
|
882
|
+
if (configuredPrice !== void 0) {
|
|
883
|
+
if ((model === "banana" || model === "gemini3") && resolution === "4K") {
|
|
884
|
+
return configuredPrice * 2 * numImages;
|
|
885
|
+
}
|
|
886
|
+
return configuredPrice * numImages;
|
|
887
|
+
}
|
|
888
|
+
switch (model) {
|
|
889
|
+
case "gpt2":
|
|
890
|
+
return 0.211 * numImages;
|
|
891
|
+
case "gpt":
|
|
892
|
+
return 0.133 * numImages;
|
|
893
|
+
case "banana":
|
|
894
|
+
case "gemini3":
|
|
895
|
+
return (resolution === "4K" ? 0.3 : 0.15) * numImages;
|
|
896
|
+
case "gemini":
|
|
897
|
+
return 0.039 * numImages;
|
|
898
|
+
case "flux":
|
|
899
|
+
case "ideogram":
|
|
900
|
+
return 0.06 * numImages;
|
|
901
|
+
case "recraft":
|
|
902
|
+
return 0.04 * numImages;
|
|
903
|
+
case "flux-fast":
|
|
904
|
+
return 3e-3 * numImages;
|
|
905
|
+
case "clarity":
|
|
906
|
+
case "crystal":
|
|
907
|
+
case "rmbg":
|
|
908
|
+
case "bria":
|
|
909
|
+
return 0.02 * numImages;
|
|
910
|
+
default:
|
|
911
|
+
return 0.1 * numImages;
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
function estimateVideoCost(durationSeconds = 5, generateAudio = true) {
|
|
915
|
+
const perSecond = generateAudio ? 0.168 : 0.112;
|
|
916
|
+
return perSecond * durationSeconds;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
// src/env.ts
|
|
920
|
+
import { defineEnv } from "@howells/envy";
|
|
921
|
+
import { z } from "zod";
|
|
922
|
+
var motifEnvSchema = defineEnv({
|
|
923
|
+
optional: {
|
|
924
|
+
FAL_KEY: z.string().min(1)
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
function parseMotifEnv(input = process.env) {
|
|
928
|
+
return motifEnvSchema.parse(input);
|
|
929
|
+
}
|
|
930
|
+
function getFalKeyFromEnv(input = process.env) {
|
|
931
|
+
return parseMotifEnv(input).FAL_KEY;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// src/generate.ts
|
|
935
|
+
var GPT_IMAGE_SIZES = ["auto", "1024x1024", "1536x1024", "1024x1536"];
|
|
936
|
+
var FAL_IMAGE_SIZE_PRESETS = [
|
|
937
|
+
"auto",
|
|
938
|
+
"square_hd",
|
|
939
|
+
"square",
|
|
940
|
+
"portrait_4_3",
|
|
941
|
+
"portrait_16_9",
|
|
942
|
+
"landscape_4_3",
|
|
943
|
+
"landscape_16_9"
|
|
944
|
+
];
|
|
945
|
+
function normalizeImageSize(imageSize, sizeMode) {
|
|
946
|
+
if (imageSize === void 0) {
|
|
947
|
+
return void 0;
|
|
948
|
+
}
|
|
949
|
+
if (typeof imageSize !== "string") {
|
|
950
|
+
if (sizeMode === "gpt_size") {
|
|
951
|
+
throw new Error("GPT Image 1.5 image_size must be auto or a fixed size");
|
|
952
|
+
}
|
|
953
|
+
return imageSize;
|
|
954
|
+
}
|
|
955
|
+
if (sizeMode === "gpt_size") {
|
|
956
|
+
if (!GPT_IMAGE_SIZES.includes(imageSize)) {
|
|
957
|
+
throw new Error(
|
|
958
|
+
`GPT Image 1.5 image_size must be one of ${GPT_IMAGE_SIZES.join(", ")}`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
return imageSize;
|
|
962
|
+
}
|
|
963
|
+
if (FAL_IMAGE_SIZE_PRESETS.includes(imageSize)) {
|
|
964
|
+
return imageSize;
|
|
965
|
+
}
|
|
966
|
+
const match = imageSize.match(/^(\d+)x(\d+)$/);
|
|
967
|
+
if (match) {
|
|
968
|
+
const width = Number(match[1]);
|
|
969
|
+
const height = Number(match[2]);
|
|
970
|
+
return { width, height };
|
|
971
|
+
}
|
|
972
|
+
throw new Error(
|
|
973
|
+
`fal image_size must be one of ${FAL_IMAGE_SIZE_PRESETS.join(", ")} or WIDTHxHEIGHT`
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
function unsupported(config, option) {
|
|
977
|
+
throw new Error(`${config.name} does not support ${option}`);
|
|
978
|
+
}
|
|
979
|
+
function validateGenerateOptions(options, config, sizeMode, hasEditImages) {
|
|
980
|
+
if (options.editImageUrls?.length && config.maxReferenceImages !== void 0 && options.editImageUrls.length > config.maxReferenceImages) {
|
|
981
|
+
throw new Error(
|
|
982
|
+
`${config.name} supports at most ${config.maxReferenceImages} reference images`
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
if (options.editImageUrls?.length && !config.supportsEdit) {
|
|
986
|
+
unsupported(config, "image editing");
|
|
987
|
+
}
|
|
988
|
+
if (options.aspect !== void 0 && sizeMode === "none") {
|
|
989
|
+
unsupported(config, "aspect");
|
|
990
|
+
}
|
|
991
|
+
if (options.resolution !== void 0 && options.resolution !== "2K" && !config.supportsResolution) {
|
|
992
|
+
unsupported(config, "resolution");
|
|
993
|
+
}
|
|
994
|
+
if (options.numImages !== void 0 && options.numImages !== 1 && !config.supportsNumImages) {
|
|
995
|
+
unsupported(config, "numImages");
|
|
996
|
+
}
|
|
997
|
+
if (options.background !== void 0 && !config.supportsBackground) {
|
|
998
|
+
unsupported(config, "background");
|
|
999
|
+
}
|
|
1000
|
+
if (options.transparent && !(config.supportsBackground || config.supportsOutputFormat)) {
|
|
1001
|
+
unsupported(config, "transparent output");
|
|
1002
|
+
}
|
|
1003
|
+
if (options.inputFidelity !== void 0) {
|
|
1004
|
+
if (!hasEditImages) {
|
|
1005
|
+
throw new Error("inputFidelity requires editImageUrls");
|
|
1006
|
+
}
|
|
1007
|
+
if (config.sizeMode !== "gpt_size" && config.name !== "GPT Image 2") {
|
|
1008
|
+
unsupported(config, "inputFidelity");
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
if (options.imageSize !== void 0 && sizeMode !== "gpt_size" && sizeMode !== "image_size_enum") {
|
|
1012
|
+
unsupported(config, "imageSize");
|
|
1013
|
+
}
|
|
1014
|
+
if (options.imagePromptStrength !== void 0 && !config.supportsImagePromptStrength) {
|
|
1015
|
+
unsupported(config, "imagePromptStrength");
|
|
1016
|
+
}
|
|
1017
|
+
if (options.imagePromptStrength !== void 0 && !hasEditImages) {
|
|
1018
|
+
throw new Error("imagePromptStrength requires editImageUrls");
|
|
1019
|
+
}
|
|
1020
|
+
if (options.maskImageUrl !== void 0 && !config.supportsMaskImage) {
|
|
1021
|
+
unsupported(config, "maskImageUrl");
|
|
1022
|
+
}
|
|
1023
|
+
if (options.maskImageUrl !== void 0 && !hasEditImages) {
|
|
1024
|
+
throw new Error("maskImageUrl requires editImageUrls");
|
|
1025
|
+
}
|
|
1026
|
+
if (options.seed !== void 0 && !config.supportsSeed) {
|
|
1027
|
+
unsupported(config, "seed");
|
|
1028
|
+
}
|
|
1029
|
+
if (options.outputFormat !== void 0 && !config.supportsOutputFormat) {
|
|
1030
|
+
unsupported(config, "outputFormat");
|
|
1031
|
+
}
|
|
1032
|
+
if (options.quality !== void 0 && !config.supportsQuality) {
|
|
1033
|
+
unsupported(config, "quality");
|
|
1034
|
+
}
|
|
1035
|
+
if (options.negativePrompt !== void 0 && !config.supportsNegativePrompt) {
|
|
1036
|
+
unsupported(config, "negativePrompt");
|
|
1037
|
+
}
|
|
1038
|
+
if (options.style !== void 0 && !config.supportsStyle) {
|
|
1039
|
+
unsupported(config, "style");
|
|
1040
|
+
}
|
|
1041
|
+
if (options.syncMode !== void 0 && !config.supportsSyncMode) {
|
|
1042
|
+
unsupported(config, "syncMode");
|
|
1043
|
+
}
|
|
1044
|
+
if (options.renderingSpeed !== void 0 && !config.supportsRenderingSpeed) {
|
|
1045
|
+
unsupported(config, "renderingSpeed");
|
|
1046
|
+
}
|
|
1047
|
+
if (options.guidanceScale !== void 0 && !config.supportsGuidanceScale) {
|
|
1048
|
+
unsupported(config, "guidanceScale");
|
|
1049
|
+
}
|
|
1050
|
+
if (options.numInferenceSteps !== void 0 && !config.supportsInferenceSteps) {
|
|
1051
|
+
unsupported(config, "numInferenceSteps");
|
|
1052
|
+
}
|
|
1053
|
+
if (options.raw !== void 0 && !config.supportsRaw) {
|
|
1054
|
+
unsupported(config, "raw");
|
|
1055
|
+
}
|
|
1056
|
+
if (options.enhancePrompt !== void 0 && !config.supportsEnhancePrompt) {
|
|
1057
|
+
unsupported(config, "enhancePrompt");
|
|
1058
|
+
}
|
|
1059
|
+
if (options.safetyTolerance !== void 0 && !config.supportsSafetyTolerance) {
|
|
1060
|
+
unsupported(config, "safetyTolerance");
|
|
1061
|
+
}
|
|
1062
|
+
if (options.enableWebSearch !== void 0 && !config.supportsWebSearch) {
|
|
1063
|
+
unsupported(config, "enableWebSearch");
|
|
1064
|
+
}
|
|
1065
|
+
if (options.enableGoogleSearch !== void 0 && !config.supportsGoogleSearch) {
|
|
1066
|
+
unsupported(config, "enableGoogleSearch");
|
|
1067
|
+
}
|
|
1068
|
+
if (options.enableSafetyChecker !== void 0 && !config.supportsSafetyChecker) {
|
|
1069
|
+
unsupported(config, "enableSafetyChecker");
|
|
1070
|
+
}
|
|
1071
|
+
if (options.limitGenerations !== void 0 && !config.supportsLimitGenerations) {
|
|
1072
|
+
unsupported(config, "limitGenerations");
|
|
1073
|
+
}
|
|
1074
|
+
if (options.thinkingLevel !== void 0 && !config.supportsThinkingLevel) {
|
|
1075
|
+
unsupported(config, "thinkingLevel");
|
|
1076
|
+
}
|
|
1077
|
+
if (options.expandPrompt !== void 0 && !config.supportsExpandPrompt) {
|
|
1078
|
+
unsupported(config, "expandPrompt");
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
function buildGenerateBody(options) {
|
|
1082
|
+
const {
|
|
1083
|
+
prompt,
|
|
1084
|
+
model,
|
|
1085
|
+
aspect = "1:1",
|
|
1086
|
+
resolution = "2K",
|
|
1087
|
+
numImages = 1,
|
|
1088
|
+
background,
|
|
1089
|
+
editImageUrls,
|
|
1090
|
+
enableGoogleSearch,
|
|
1091
|
+
enableSafetyChecker,
|
|
1092
|
+
transparent,
|
|
1093
|
+
inputFidelity,
|
|
1094
|
+
imageSize,
|
|
1095
|
+
imagePromptStrength,
|
|
1096
|
+
limitGenerations,
|
|
1097
|
+
maskImageUrl,
|
|
1098
|
+
seed,
|
|
1099
|
+
outputFormat,
|
|
1100
|
+
quality,
|
|
1101
|
+
negativePrompt,
|
|
1102
|
+
style,
|
|
1103
|
+
syncMode,
|
|
1104
|
+
thinkingLevel,
|
|
1105
|
+
renderingSpeed,
|
|
1106
|
+
guidanceScale,
|
|
1107
|
+
numInferenceSteps,
|
|
1108
|
+
raw,
|
|
1109
|
+
enhancePrompt,
|
|
1110
|
+
safetyTolerance,
|
|
1111
|
+
enableWebSearch,
|
|
1112
|
+
expandPrompt
|
|
1113
|
+
} = options;
|
|
1114
|
+
const config = MODELS[model];
|
|
1115
|
+
if (!config) {
|
|
1116
|
+
throw new Error(`Unknown model: ${model}`);
|
|
1117
|
+
}
|
|
1118
|
+
let endpoint = config.endpoint;
|
|
1119
|
+
const body = { prompt };
|
|
1120
|
+
const hasEditImages = Boolean(editImageUrls?.length);
|
|
1121
|
+
const editImages = editImageUrls ?? [];
|
|
1122
|
+
const explicitAspect = options.aspect !== void 0;
|
|
1123
|
+
const sizeMode = config.sizeMode ?? "aspect_ratio";
|
|
1124
|
+
validateGenerateOptions(options, config, sizeMode, hasEditImages);
|
|
1125
|
+
const normalizedImageSize = normalizeImageSize(imageSize, sizeMode);
|
|
1126
|
+
switch (sizeMode) {
|
|
1127
|
+
case "gpt_size":
|
|
1128
|
+
body.image_size = normalizedImageSize ?? (hasEditImages && !explicitAspect ? "auto" : aspectToGptSize(aspect));
|
|
1129
|
+
break;
|
|
1130
|
+
case "image_size_enum":
|
|
1131
|
+
body.image_size = normalizedImageSize ?? aspectToFalImageSize(aspect);
|
|
1132
|
+
break;
|
|
1133
|
+
case "aspect_ratio":
|
|
1134
|
+
if (config.supportsAspect) {
|
|
1135
|
+
body.aspect_ratio = aspect;
|
|
1136
|
+
}
|
|
1137
|
+
if (config.supportsResolution) {
|
|
1138
|
+
body.resolution = model === "grok-image" ? resolution.toLowerCase() : resolution;
|
|
1139
|
+
}
|
|
1140
|
+
break;
|
|
1141
|
+
}
|
|
1142
|
+
if (config.supportsQuality) {
|
|
1143
|
+
body.quality = quality ?? "high";
|
|
1144
|
+
}
|
|
1145
|
+
if (config.supportsBackground && background) {
|
|
1146
|
+
body.background = background;
|
|
1147
|
+
}
|
|
1148
|
+
if (config.supportsBackground && transparent) {
|
|
1149
|
+
body.background = "transparent";
|
|
1150
|
+
}
|
|
1151
|
+
if (transparent && config.supportsOutputFormat) {
|
|
1152
|
+
body.output_format = "png";
|
|
1153
|
+
}
|
|
1154
|
+
if (syncMode !== void 0 && config.supportsSyncMode) {
|
|
1155
|
+
body.sync_mode = syncMode;
|
|
1156
|
+
}
|
|
1157
|
+
if (seed !== void 0 && config.supportsSeed) {
|
|
1158
|
+
body.seed = seed;
|
|
1159
|
+
}
|
|
1160
|
+
if (outputFormat && config.supportsOutputFormat && !transparent) {
|
|
1161
|
+
if (config.supportedOutputFormats && !config.supportedOutputFormats.includes(outputFormat)) {
|
|
1162
|
+
throw new Error(
|
|
1163
|
+
`${config.name} supports output formats: ${config.supportedOutputFormats.join(", ")}`
|
|
1164
|
+
);
|
|
1165
|
+
}
|
|
1166
|
+
body.output_format = outputFormat;
|
|
1167
|
+
}
|
|
1168
|
+
if (config.supportsRaw && raw !== void 0) {
|
|
1169
|
+
body.raw = raw;
|
|
1170
|
+
}
|
|
1171
|
+
if (config.supportsEnhancePrompt && enhancePrompt !== void 0) {
|
|
1172
|
+
body.enhance_prompt = enhancePrompt;
|
|
1173
|
+
}
|
|
1174
|
+
if (config.supportsImagePromptStrength && imagePromptStrength !== void 0) {
|
|
1175
|
+
body.image_prompt_strength = imagePromptStrength;
|
|
1176
|
+
}
|
|
1177
|
+
if (model === "flux") {
|
|
1178
|
+
if (safetyTolerance) body.safety_tolerance = safetyTolerance;
|
|
1179
|
+
}
|
|
1180
|
+
if (config.supportsGuidanceScale && guidanceScale !== void 0) {
|
|
1181
|
+
body.guidance_scale = guidanceScale;
|
|
1182
|
+
}
|
|
1183
|
+
if (config.supportsInferenceSteps && numInferenceSteps !== void 0) {
|
|
1184
|
+
body.num_inference_steps = numInferenceSteps;
|
|
1185
|
+
}
|
|
1186
|
+
if (config.supportsSafetyTolerance) {
|
|
1187
|
+
if (safetyTolerance) body.safety_tolerance = safetyTolerance;
|
|
1188
|
+
}
|
|
1189
|
+
if (config.supportsWebSearch) {
|
|
1190
|
+
if (enableWebSearch !== void 0) body.enable_web_search = enableWebSearch;
|
|
1191
|
+
}
|
|
1192
|
+
if (config.supportsGoogleSearch) {
|
|
1193
|
+
if (enableGoogleSearch !== void 0) {
|
|
1194
|
+
body.enable_google_search = enableGoogleSearch;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
if (config.supportsSafetyChecker) {
|
|
1198
|
+
if (enableSafetyChecker !== void 0) {
|
|
1199
|
+
body.enable_safety_checker = enableSafetyChecker;
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
if (config.supportsLimitGenerations) {
|
|
1203
|
+
if (limitGenerations !== void 0) {
|
|
1204
|
+
body.limit_generations = limitGenerations;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
if (config.supportsThinkingLevel) {
|
|
1208
|
+
if (thinkingLevel) body.thinking_level = thinkingLevel;
|
|
1209
|
+
}
|
|
1210
|
+
if (config.supportsStyle && style) {
|
|
1211
|
+
body.style = style;
|
|
1212
|
+
}
|
|
1213
|
+
if (config.supportsNegativePrompt) {
|
|
1214
|
+
if (negativePrompt) body.negative_prompt = negativePrompt;
|
|
1215
|
+
}
|
|
1216
|
+
if (model === "ideogram") {
|
|
1217
|
+
if (style) body.style = style;
|
|
1218
|
+
}
|
|
1219
|
+
if (config.supportsRenderingSpeed) {
|
|
1220
|
+
if (renderingSpeed) body.rendering_speed = renderingSpeed;
|
|
1221
|
+
}
|
|
1222
|
+
if (config.supportsExpandPrompt) {
|
|
1223
|
+
if (expandPrompt !== void 0) body.expand_prompt = expandPrompt;
|
|
1224
|
+
}
|
|
1225
|
+
if (model === "flux" && hasEditImages) {
|
|
1226
|
+
body.image_url = editImages[0];
|
|
1227
|
+
body.image_prompt_strength = imagePromptStrength ?? 0.5;
|
|
1228
|
+
}
|
|
1229
|
+
if (config.supportsNumImages) {
|
|
1230
|
+
body.num_images = numImages;
|
|
1231
|
+
}
|
|
1232
|
+
if (hasEditImages && sizeMode !== "gpt_size" && model !== "flux") {
|
|
1233
|
+
if (!config.supportsEdit) {
|
|
1234
|
+
throw new Error(`Model ${model} does not support image editing`);
|
|
1235
|
+
}
|
|
1236
|
+
endpoint = config.editEndpoint ?? `${endpoint}/edit`;
|
|
1237
|
+
const editImagesField = config.editImagesField ?? "image_urls";
|
|
1238
|
+
body[editImagesField] = editImagesField === "image_url" ? editImages[0] : editImages;
|
|
1239
|
+
if (model === "gpt2" && inputFidelity) {
|
|
1240
|
+
body.input_fidelity = inputFidelity;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
if (hasEditImages && sizeMode === "gpt_size") {
|
|
1244
|
+
endpoint = config.editEndpoint ?? `${endpoint}/edit`;
|
|
1245
|
+
body.image_urls = editImages;
|
|
1246
|
+
if (inputFidelity) {
|
|
1247
|
+
body.input_fidelity = inputFidelity;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
if (maskImageUrl && config.supportsMaskImage) {
|
|
1251
|
+
body.mask_image_url = maskImageUrl;
|
|
1252
|
+
}
|
|
1253
|
+
return { endpoint, body };
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// src/leaderboards.ts
|
|
1257
|
+
var IMAGE_TEXT_TO_IMAGE_TOP_20 = {
|
|
1258
|
+
capturedAt: "2026-05-12",
|
|
1259
|
+
source: "https://artificialanalysis.ai/image/leaderboard/text-to-image",
|
|
1260
|
+
entries: [
|
|
1261
|
+
{
|
|
1262
|
+
rank: 1,
|
|
1263
|
+
creator: "OpenAI",
|
|
1264
|
+
model: "GPT Image 2 (high)",
|
|
1265
|
+
elo: 1337,
|
|
1266
|
+
apiPricing: "$211/1k images",
|
|
1267
|
+
isCurrent: true,
|
|
1268
|
+
routableModelId: "gpt2"
|
|
1269
|
+
},
|
|
1270
|
+
{
|
|
1271
|
+
rank: 2,
|
|
1272
|
+
creator: "OpenAI",
|
|
1273
|
+
model: "GPT Image 1.5 (high)",
|
|
1274
|
+
elo: 1268,
|
|
1275
|
+
apiPricing: "$133/1k images",
|
|
1276
|
+
isCurrent: true,
|
|
1277
|
+
routableModelId: "gpt"
|
|
1278
|
+
},
|
|
1279
|
+
{
|
|
1280
|
+
rank: 3,
|
|
1281
|
+
creator: "Google",
|
|
1282
|
+
model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
|
|
1283
|
+
elo: 1263,
|
|
1284
|
+
apiPricing: "$67/1k images",
|
|
1285
|
+
isCurrent: true,
|
|
1286
|
+
routableModelId: "banana2"
|
|
1287
|
+
},
|
|
1288
|
+
{
|
|
1289
|
+
rank: 4,
|
|
1290
|
+
creator: "Sourceful",
|
|
1291
|
+
model: "Riverflow 2.0",
|
|
1292
|
+
elo: 1256,
|
|
1293
|
+
apiPricing: "$150/1k images",
|
|
1294
|
+
isCurrent: true
|
|
1295
|
+
},
|
|
1296
|
+
{
|
|
1297
|
+
rank: 5,
|
|
1298
|
+
creator: "Google",
|
|
1299
|
+
model: "Nano Banana Pro (Gemini 3 Pro Image)",
|
|
1300
|
+
elo: 1220,
|
|
1301
|
+
apiPricing: "$134/1k images",
|
|
1302
|
+
isCurrent: true,
|
|
1303
|
+
routableModelId: "banana"
|
|
1304
|
+
},
|
|
1305
|
+
{
|
|
1306
|
+
rank: 6,
|
|
1307
|
+
creator: "ByteDance Seed",
|
|
1308
|
+
model: "Seedream 4.0",
|
|
1309
|
+
elo: 1198,
|
|
1310
|
+
apiPricing: "$30/1k images",
|
|
1311
|
+
isCurrent: true,
|
|
1312
|
+
routableModelId: "seedream4"
|
|
1313
|
+
},
|
|
1314
|
+
{
|
|
1315
|
+
rank: 7,
|
|
1316
|
+
creator: "Microsoft",
|
|
1317
|
+
model: "MAI-Image-2",
|
|
1318
|
+
elo: 1198,
|
|
1319
|
+
apiPricing: "$50/1k images",
|
|
1320
|
+
isCurrent: true
|
|
1321
|
+
},
|
|
1322
|
+
{
|
|
1323
|
+
rank: 8,
|
|
1324
|
+
creator: "Black Forest Labs",
|
|
1325
|
+
model: "FLUX.2 [max]",
|
|
1326
|
+
elo: 1197,
|
|
1327
|
+
apiPricing: "$70/1k images",
|
|
1328
|
+
isCurrent: true,
|
|
1329
|
+
routableModelId: "flux2-max"
|
|
1330
|
+
},
|
|
1331
|
+
{
|
|
1332
|
+
rank: 9,
|
|
1333
|
+
creator: "Peanut",
|
|
1334
|
+
model: "Peanut (Open Weights Coming Soon)",
|
|
1335
|
+
elo: 1187,
|
|
1336
|
+
apiPricing: "Unavailable",
|
|
1337
|
+
isCurrent: true
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
rank: 10,
|
|
1341
|
+
creator: "Black Forest Labs",
|
|
1342
|
+
model: "FLUX.2 [pro]",
|
|
1343
|
+
elo: 1186,
|
|
1344
|
+
apiPricing: "$30/1k images",
|
|
1345
|
+
isCurrent: true,
|
|
1346
|
+
routableModelId: "flux2-pro"
|
|
1347
|
+
},
|
|
1348
|
+
{
|
|
1349
|
+
rank: 11,
|
|
1350
|
+
creator: "Google",
|
|
1351
|
+
model: "Imagen 4 Ultra Preview 0606",
|
|
1352
|
+
elo: 1184,
|
|
1353
|
+
apiPricing: "$60/1k images",
|
|
1354
|
+
isCurrent: false
|
|
1355
|
+
},
|
|
1356
|
+
{
|
|
1357
|
+
rank: 12,
|
|
1358
|
+
creator: "xAI",
|
|
1359
|
+
model: "grok-imagine-image",
|
|
1360
|
+
elo: 1182,
|
|
1361
|
+
apiPricing: "$20/1k images",
|
|
1362
|
+
isCurrent: true,
|
|
1363
|
+
routableModelId: "grok-image"
|
|
1364
|
+
},
|
|
1365
|
+
{
|
|
1366
|
+
rank: 13,
|
|
1367
|
+
creator: "Black Forest Labs",
|
|
1368
|
+
model: "FLUX.2 [flex]",
|
|
1369
|
+
elo: 1182,
|
|
1370
|
+
apiPricing: "$60/1k images",
|
|
1371
|
+
isCurrent: true,
|
|
1372
|
+
routableModelId: "flux2-flex"
|
|
1373
|
+
},
|
|
1374
|
+
{
|
|
1375
|
+
rank: 14,
|
|
1376
|
+
creator: "ImagineArt",
|
|
1377
|
+
model: "ImagineArt 2.0",
|
|
1378
|
+
elo: 1181,
|
|
1379
|
+
apiPricing: "$30/1k images",
|
|
1380
|
+
isCurrent: true
|
|
1381
|
+
},
|
|
1382
|
+
{
|
|
1383
|
+
rank: 15,
|
|
1384
|
+
creator: "Google",
|
|
1385
|
+
model: "Imagen 4 Ultra",
|
|
1386
|
+
elo: 1171,
|
|
1387
|
+
apiPricing: "$60/1k images",
|
|
1388
|
+
isCurrent: true
|
|
1389
|
+
},
|
|
1390
|
+
{
|
|
1391
|
+
rank: 16,
|
|
1392
|
+
creator: "Google",
|
|
1393
|
+
model: "Imagen 4 Preview 0606",
|
|
1394
|
+
elo: 1169,
|
|
1395
|
+
apiPricing: "$40/1k images",
|
|
1396
|
+
isCurrent: false
|
|
1397
|
+
},
|
|
1398
|
+
{
|
|
1399
|
+
rank: 17,
|
|
1400
|
+
creator: "ByteDance Seed",
|
|
1401
|
+
model: "Seedream 4.5",
|
|
1402
|
+
elo: 1167,
|
|
1403
|
+
apiPricing: "$40/1k images",
|
|
1404
|
+
isCurrent: true,
|
|
1405
|
+
routableModelId: "seedream45"
|
|
1406
|
+
},
|
|
1407
|
+
{
|
|
1408
|
+
rank: 18,
|
|
1409
|
+
creator: "Fal",
|
|
1410
|
+
model: "FLUX.2 [dev] Turbo",
|
|
1411
|
+
elo: 1161,
|
|
1412
|
+
apiPricing: "$8/1k images",
|
|
1413
|
+
isCurrent: true
|
|
1414
|
+
},
|
|
1415
|
+
{
|
|
1416
|
+
rank: 19,
|
|
1417
|
+
creator: "Black Forest Labs",
|
|
1418
|
+
model: "FLUX.2 [dev]",
|
|
1419
|
+
elo: 1160,
|
|
1420
|
+
apiPricing: "$12/1k images",
|
|
1421
|
+
isCurrent: true,
|
|
1422
|
+
routableModelId: "flux2-dev"
|
|
1423
|
+
},
|
|
1424
|
+
{
|
|
1425
|
+
rank: 20,
|
|
1426
|
+
creator: "Alibaba",
|
|
1427
|
+
model: "Qwen Image Max 2512",
|
|
1428
|
+
elo: 1158,
|
|
1429
|
+
apiPricing: "$20/1k images",
|
|
1430
|
+
isCurrent: true,
|
|
1431
|
+
routableModelId: "qwen"
|
|
1432
|
+
}
|
|
1433
|
+
]
|
|
1434
|
+
};
|
|
1435
|
+
var IMAGE_EDITING_TOP_20 = {
|
|
1436
|
+
capturedAt: "2026-05-12",
|
|
1437
|
+
source: "https://artificialanalysis.ai/image/leaderboard/editing",
|
|
1438
|
+
entries: [
|
|
1439
|
+
{
|
|
1440
|
+
rank: 1,
|
|
1441
|
+
creator: "Sourceful",
|
|
1442
|
+
model: "Riverflow 2.0",
|
|
1443
|
+
elo: 1286,
|
|
1444
|
+
apiPricing: "$150/1k images",
|
|
1445
|
+
isCurrent: true
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
rank: 2,
|
|
1449
|
+
creator: "OpenAI",
|
|
1450
|
+
model: "GPT Image 1.5 (high)",
|
|
1451
|
+
elo: 1262,
|
|
1452
|
+
apiPricing: "$133/1k images",
|
|
1453
|
+
isCurrent: true,
|
|
1454
|
+
routableModelId: "gpt"
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
rank: 3,
|
|
1458
|
+
creator: "OpenAI",
|
|
1459
|
+
model: "GPT Image 2 (high)",
|
|
1460
|
+
elo: 1249,
|
|
1461
|
+
apiPricing: "$211/1k images",
|
|
1462
|
+
isCurrent: true,
|
|
1463
|
+
routableModelId: "gpt2"
|
|
1464
|
+
},
|
|
1465
|
+
{
|
|
1466
|
+
rank: 4,
|
|
1467
|
+
creator: "Google",
|
|
1468
|
+
model: "Nano Banana Pro (Gemini 3 Pro Image)",
|
|
1469
|
+
elo: 1241,
|
|
1470
|
+
apiPricing: "$134/1k images",
|
|
1471
|
+
isCurrent: true,
|
|
1472
|
+
routableModelId: "banana"
|
|
1473
|
+
},
|
|
1474
|
+
{
|
|
1475
|
+
rank: 5,
|
|
1476
|
+
creator: "Google",
|
|
1477
|
+
model: "Nano Banana 2 (Gemini 3.1 Flash Image Preview)",
|
|
1478
|
+
elo: 1231,
|
|
1479
|
+
apiPricing: "$67/1k images",
|
|
1480
|
+
isCurrent: true,
|
|
1481
|
+
routableModelId: "banana2"
|
|
1482
|
+
},
|
|
1483
|
+
{
|
|
1484
|
+
rank: 6,
|
|
1485
|
+
creator: "Tencent",
|
|
1486
|
+
model: "HunyuanImage 3.0 Instruct (Fal)",
|
|
1487
|
+
elo: 1222,
|
|
1488
|
+
apiPricing: "$90/1k images",
|
|
1489
|
+
isCurrent: true
|
|
1490
|
+
},
|
|
1491
|
+
{
|
|
1492
|
+
rank: 7,
|
|
1493
|
+
creator: "xAI",
|
|
1494
|
+
model: "grok-imagine-image",
|
|
1495
|
+
elo: 1213,
|
|
1496
|
+
apiPricing: "$20/1k images",
|
|
1497
|
+
isCurrent: true,
|
|
1498
|
+
routableModelId: "grok-image"
|
|
1499
|
+
},
|
|
1500
|
+
{
|
|
1501
|
+
rank: 8,
|
|
1502
|
+
creator: "xAI",
|
|
1503
|
+
model: "grok-imagine-image-pro",
|
|
1504
|
+
elo: 1212,
|
|
1505
|
+
apiPricing: "$70/1k images",
|
|
1506
|
+
isCurrent: true
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
rank: 9,
|
|
1510
|
+
creator: "KlingAI",
|
|
1511
|
+
model: "Kling Image 3.0 Omni",
|
|
1512
|
+
elo: 1207,
|
|
1513
|
+
apiPricing: "Unavailable",
|
|
1514
|
+
isCurrent: true
|
|
1515
|
+
},
|
|
1516
|
+
{
|
|
1517
|
+
rank: 10,
|
|
1518
|
+
creator: "Black Forest Labs",
|
|
1519
|
+
model: "FLUX.2 [max]",
|
|
1520
|
+
elo: 1206,
|
|
1521
|
+
apiPricing: "$140/1k images",
|
|
1522
|
+
isCurrent: true,
|
|
1523
|
+
routableModelId: "flux2-max"
|
|
1524
|
+
},
|
|
1525
|
+
{
|
|
1526
|
+
rank: 11,
|
|
1527
|
+
creator: "Alibaba",
|
|
1528
|
+
model: "Wan 2.7 Pro",
|
|
1529
|
+
elo: 1200,
|
|
1530
|
+
apiPricing: "Unavailable",
|
|
1531
|
+
isCurrent: true
|
|
1532
|
+
},
|
|
1533
|
+
{
|
|
1534
|
+
rank: 12,
|
|
1535
|
+
creator: "KlingAI",
|
|
1536
|
+
model: "Kling Image 3.0",
|
|
1537
|
+
elo: 1196,
|
|
1538
|
+
apiPricing: "$28/1k images",
|
|
1539
|
+
isCurrent: true
|
|
1540
|
+
},
|
|
1541
|
+
{
|
|
1542
|
+
rank: 13,
|
|
1543
|
+
creator: "KlingAI",
|
|
1544
|
+
model: "Kling Image O1",
|
|
1545
|
+
elo: 1193,
|
|
1546
|
+
apiPricing: "$28/1k images",
|
|
1547
|
+
isCurrent: true
|
|
1548
|
+
},
|
|
1549
|
+
{
|
|
1550
|
+
rank: 14,
|
|
1551
|
+
creator: "Alibaba",
|
|
1552
|
+
model: "Wan 2.6 Image",
|
|
1553
|
+
elo: 1188,
|
|
1554
|
+
apiPricing: "$30/1k images",
|
|
1555
|
+
isCurrent: true
|
|
1556
|
+
},
|
|
1557
|
+
{
|
|
1558
|
+
rank: 15,
|
|
1559
|
+
creator: "Sourceful",
|
|
1560
|
+
model: "Riverflow 1",
|
|
1561
|
+
elo: 1184,
|
|
1562
|
+
apiPricing: "$66/1k images",
|
|
1563
|
+
isCurrent: true
|
|
1564
|
+
},
|
|
1565
|
+
{
|
|
1566
|
+
rank: 16,
|
|
1567
|
+
creator: "ByteDance Seed",
|
|
1568
|
+
model: "Seedream 4.0",
|
|
1569
|
+
elo: 1184,
|
|
1570
|
+
apiPricing: "$30/1k images",
|
|
1571
|
+
isCurrent: true,
|
|
1572
|
+
routableModelId: "seedream4"
|
|
1573
|
+
},
|
|
1574
|
+
{
|
|
1575
|
+
rank: 17,
|
|
1576
|
+
creator: "ByteDance Seed",
|
|
1577
|
+
model: "Seedream 4.5",
|
|
1578
|
+
elo: 1184,
|
|
1579
|
+
apiPricing: "$40/1k images",
|
|
1580
|
+
isCurrent: true,
|
|
1581
|
+
routableModelId: "seedream45"
|
|
1582
|
+
},
|
|
1583
|
+
{
|
|
1584
|
+
rank: 18,
|
|
1585
|
+
creator: "Alibaba",
|
|
1586
|
+
model: "Wan 2.7",
|
|
1587
|
+
elo: 1181,
|
|
1588
|
+
apiPricing: "$26/1k images",
|
|
1589
|
+
isCurrent: true
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
rank: 19,
|
|
1593
|
+
creator: "Google",
|
|
1594
|
+
model: "Nano Banana (Gemini 2.5 Flash Image)",
|
|
1595
|
+
elo: 1173,
|
|
1596
|
+
apiPricing: "$39/1k images",
|
|
1597
|
+
isCurrent: true,
|
|
1598
|
+
routableModelId: "gemini"
|
|
1599
|
+
},
|
|
1600
|
+
{
|
|
1601
|
+
rank: 20,
|
|
1602
|
+
creator: "Reve",
|
|
1603
|
+
model: "Reve V1 (December)",
|
|
1604
|
+
elo: 1172,
|
|
1605
|
+
apiPricing: "$40/1k images",
|
|
1606
|
+
isCurrent: true
|
|
1607
|
+
}
|
|
1608
|
+
]
|
|
1609
|
+
};
|
|
1610
|
+
var VIDEO_TEXT_TO_VIDEO_TOP_15 = {
|
|
1611
|
+
capturedAt: "2026-05-12",
|
|
1612
|
+
source: "https://artificialanalysis.ai/video/leaderboard/text-to-video",
|
|
1613
|
+
entries: [
|
|
1614
|
+
{
|
|
1615
|
+
rank: 1,
|
|
1616
|
+
creator: "Alibaba-ATH",
|
|
1617
|
+
model: "HappyHorse-1.0",
|
|
1618
|
+
elo: 1354,
|
|
1619
|
+
apiPricing: "$14.40/min"
|
|
1620
|
+
},
|
|
1621
|
+
{
|
|
1622
|
+
rank: 2,
|
|
1623
|
+
creator: "ByteDance Seed",
|
|
1624
|
+
model: "Dreamina Seedance 2.0 720p",
|
|
1625
|
+
elo: 1273,
|
|
1626
|
+
apiPricing: "No API available"
|
|
1627
|
+
},
|
|
1628
|
+
{
|
|
1629
|
+
rank: 3,
|
|
1630
|
+
creator: "KlingAI",
|
|
1631
|
+
model: "Kling 3.0 1080p (Pro)",
|
|
1632
|
+
elo: 1249,
|
|
1633
|
+
apiPricing: "$13.44/min"
|
|
1634
|
+
},
|
|
1635
|
+
{
|
|
1636
|
+
rank: 4,
|
|
1637
|
+
creator: "KlingAI",
|
|
1638
|
+
model: "Kling 3.0 Omni 1080p (Pro)",
|
|
1639
|
+
elo: 1233,
|
|
1640
|
+
apiPricing: "$13.44/min"
|
|
1641
|
+
},
|
|
1642
|
+
{
|
|
1643
|
+
rank: 5,
|
|
1644
|
+
creator: "xAI",
|
|
1645
|
+
model: "grok-imagine-video",
|
|
1646
|
+
elo: 1233,
|
|
1647
|
+
apiPricing: "$4.20/min"
|
|
1648
|
+
},
|
|
1649
|
+
{
|
|
1650
|
+
rank: 6,
|
|
1651
|
+
creator: "Vidu",
|
|
1652
|
+
model: "Vidu Q3 Pro",
|
|
1653
|
+
elo: 1225,
|
|
1654
|
+
apiPricing: "$9.60/min"
|
|
1655
|
+
},
|
|
1656
|
+
{
|
|
1657
|
+
rank: 7,
|
|
1658
|
+
creator: "Video Rebirth",
|
|
1659
|
+
model: "Bach-1.0 Preview",
|
|
1660
|
+
elo: 1224,
|
|
1661
|
+
apiPricing: "$3.00/min"
|
|
1662
|
+
},
|
|
1663
|
+
{
|
|
1664
|
+
rank: 8,
|
|
1665
|
+
creator: "KlingAI",
|
|
1666
|
+
model: "Kling 3.0 Omni 720p (Standard)",
|
|
1667
|
+
elo: 1224,
|
|
1668
|
+
apiPricing: "$10.08/min"
|
|
1669
|
+
},
|
|
1670
|
+
{
|
|
1671
|
+
rank: 9,
|
|
1672
|
+
creator: "PixVerse",
|
|
1673
|
+
model: "PixVerse V6",
|
|
1674
|
+
elo: 1222,
|
|
1675
|
+
apiPricing: "$5.40/min"
|
|
1676
|
+
},
|
|
1677
|
+
{
|
|
1678
|
+
rank: 10,
|
|
1679
|
+
creator: "PixVerse",
|
|
1680
|
+
model: "PixVerse V5.6",
|
|
1681
|
+
elo: 1221,
|
|
1682
|
+
apiPricing: "$9.00/min"
|
|
1683
|
+
},
|
|
1684
|
+
{
|
|
1685
|
+
rank: 11,
|
|
1686
|
+
creator: "Runway",
|
|
1687
|
+
model: "Runway Gen-4.5",
|
|
1688
|
+
elo: 1220,
|
|
1689
|
+
apiPricing: "No API available"
|
|
1690
|
+
},
|
|
1691
|
+
{
|
|
1692
|
+
rank: 12,
|
|
1693
|
+
creator: "Google",
|
|
1694
|
+
model: "Veo 3",
|
|
1695
|
+
elo: 1218,
|
|
1696
|
+
apiPricing: "$12.00/min"
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
rank: 13,
|
|
1700
|
+
creator: "KlingAI",
|
|
1701
|
+
model: "Kling 3.0 720p (Standard)",
|
|
1702
|
+
elo: 1215,
|
|
1703
|
+
apiPricing: "$10.08/min"
|
|
1704
|
+
},
|
|
1705
|
+
{
|
|
1706
|
+
rank: 14,
|
|
1707
|
+
creator: "Google",
|
|
1708
|
+
model: "Veo 3.1 Lite",
|
|
1709
|
+
elo: 1214,
|
|
1710
|
+
apiPricing: "$3.00/min"
|
|
1711
|
+
},
|
|
1712
|
+
{
|
|
1713
|
+
rank: 15,
|
|
1714
|
+
creator: "KlingAI",
|
|
1715
|
+
model: "Kling O1 Pro (January)",
|
|
1716
|
+
elo: 1209,
|
|
1717
|
+
apiPricing: "$10.08/min"
|
|
1718
|
+
}
|
|
1719
|
+
]
|
|
1720
|
+
};
|
|
1721
|
+
var VIDEO_IMAGE_TO_VIDEO_TOP_15 = {
|
|
1722
|
+
capturedAt: "2026-05-12",
|
|
1723
|
+
source: "https://artificialanalysis.ai/video/leaderboard/image-to-video",
|
|
1724
|
+
entries: [
|
|
1725
|
+
{
|
|
1726
|
+
rank: 1,
|
|
1727
|
+
creator: "Alibaba-ATH",
|
|
1728
|
+
model: "HappyHorse-1.0",
|
|
1729
|
+
elo: 1395,
|
|
1730
|
+
apiPricing: "Coming soon"
|
|
1731
|
+
},
|
|
1732
|
+
{
|
|
1733
|
+
rank: 2,
|
|
1734
|
+
creator: "ByteDance Seed",
|
|
1735
|
+
model: "Dreamina Seedance 2.0 720p",
|
|
1736
|
+
elo: 1348,
|
|
1737
|
+
apiPricing: "No API available"
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
rank: 3,
|
|
1741
|
+
creator: "xAI",
|
|
1742
|
+
model: "grok-imagine-video",
|
|
1743
|
+
elo: 1326,
|
|
1744
|
+
apiPricing: "$4.20/min"
|
|
1745
|
+
},
|
|
1746
|
+
{
|
|
1747
|
+
rank: 4,
|
|
1748
|
+
creator: "PixVerse",
|
|
1749
|
+
model: "PixVerse V6",
|
|
1750
|
+
elo: 1322,
|
|
1751
|
+
apiPricing: "$5.40/min"
|
|
1752
|
+
},
|
|
1753
|
+
{
|
|
1754
|
+
rank: 5,
|
|
1755
|
+
creator: "Vidu",
|
|
1756
|
+
model: "Vidu Q3 Pro",
|
|
1757
|
+
elo: 1287,
|
|
1758
|
+
apiPricing: "$9.60/min"
|
|
1759
|
+
},
|
|
1760
|
+
{
|
|
1761
|
+
rank: 6,
|
|
1762
|
+
creator: "KlingAI",
|
|
1763
|
+
model: "Kling 2.5 Turbo 1080p",
|
|
1764
|
+
elo: 1283,
|
|
1765
|
+
apiPricing: "$4.20/min"
|
|
1766
|
+
},
|
|
1767
|
+
{
|
|
1768
|
+
rank: 7,
|
|
1769
|
+
creator: "KlingAI",
|
|
1770
|
+
model: "Kling 3.0 1080p (Pro)",
|
|
1771
|
+
elo: 1280,
|
|
1772
|
+
apiPricing: "$13.44/min"
|
|
1773
|
+
},
|
|
1774
|
+
{
|
|
1775
|
+
rank: 8,
|
|
1776
|
+
creator: "PixVerse",
|
|
1777
|
+
model: "PixVerse V5.6",
|
|
1778
|
+
elo: 1279,
|
|
1779
|
+
apiPricing: "$9.00/min"
|
|
1780
|
+
},
|
|
1781
|
+
{
|
|
1782
|
+
rank: 9,
|
|
1783
|
+
creator: "KlingAI",
|
|
1784
|
+
model: "Kling 3.0 Omni 1080p (Pro)",
|
|
1785
|
+
elo: 1277,
|
|
1786
|
+
apiPricing: "$13.44/min"
|
|
1787
|
+
},
|
|
1788
|
+
{
|
|
1789
|
+
rank: 10,
|
|
1790
|
+
creator: "KlingAI",
|
|
1791
|
+
model: "Kling 2.6 Standard (January)",
|
|
1792
|
+
elo: 1271,
|
|
1793
|
+
apiPricing: "Coming soon"
|
|
1794
|
+
},
|
|
1795
|
+
{
|
|
1796
|
+
rank: 11,
|
|
1797
|
+
creator: "PixVerse",
|
|
1798
|
+
model: "PixVerse V5.5",
|
|
1799
|
+
elo: 1271,
|
|
1800
|
+
apiPricing: "$6.40/min"
|
|
1801
|
+
},
|
|
1802
|
+
{
|
|
1803
|
+
rank: 12,
|
|
1804
|
+
creator: "Google",
|
|
1805
|
+
model: "Veo 3.1 Fast",
|
|
1806
|
+
elo: 1268,
|
|
1807
|
+
apiPricing: "$6.00/min"
|
|
1808
|
+
},
|
|
1809
|
+
{
|
|
1810
|
+
rank: 13,
|
|
1811
|
+
creator: "Runway",
|
|
1812
|
+
model: "Runway Gen-4.5",
|
|
1813
|
+
elo: 1263,
|
|
1814
|
+
apiPricing: "No API available"
|
|
1815
|
+
},
|
|
1816
|
+
{
|
|
1817
|
+
rank: 14,
|
|
1818
|
+
creator: "KlingAI",
|
|
1819
|
+
model: "Kling 3.0 Omni 720p (Standard)",
|
|
1820
|
+
elo: 1263,
|
|
1821
|
+
apiPricing: "$10.08/min"
|
|
1822
|
+
},
|
|
1823
|
+
{
|
|
1824
|
+
rank: 15,
|
|
1825
|
+
creator: "KlingAI",
|
|
1826
|
+
model: "Kling 3.0 720p (Standard)",
|
|
1827
|
+
elo: 1263,
|
|
1828
|
+
apiPricing: "$10.08/min"
|
|
1829
|
+
}
|
|
1830
|
+
]
|
|
1831
|
+
};
|
|
1832
|
+
|
|
1833
|
+
// src/server.ts
|
|
1834
|
+
import { err, ok } from "neverthrow";
|
|
1835
|
+
|
|
1836
|
+
// src/tools.ts
|
|
1837
|
+
var FAL_EXPLORE_CHECKED_AT = "2026-05-12";
|
|
1838
|
+
var FAL_TOOLS = {
|
|
1839
|
+
nsfw: {
|
|
1840
|
+
name: "NSFW Checker",
|
|
1841
|
+
endpoint: "fal-ai/x-ailab/nsfw",
|
|
1842
|
+
task: "vision moderation",
|
|
1843
|
+
category: "moderation",
|
|
1844
|
+
description: "Predict whether one or more images contain NSFW concepts.",
|
|
1845
|
+
inputKind: "images",
|
|
1846
|
+
inputField: "image_urls",
|
|
1847
|
+
outputKeys: ["has_nsfw_concepts"],
|
|
1848
|
+
pricing: "$0.001/image",
|
|
1849
|
+
sourceUrl: "https://fal.ai/models/fal-ai/x-ailab/nsfw"
|
|
1850
|
+
},
|
|
1851
|
+
"topaz-image": {
|
|
1852
|
+
name: "Topaz Image Upscale",
|
|
1853
|
+
endpoint: "fal-ai/topaz/upscale/image",
|
|
1854
|
+
task: "image enhancement",
|
|
1855
|
+
category: "upscale",
|
|
1856
|
+
description: "Professional Topaz image enhancement and upscaling.",
|
|
1857
|
+
inputKind: "image",
|
|
1858
|
+
inputField: "image_url",
|
|
1859
|
+
outputKeys: ["image"],
|
|
1860
|
+
pricing: "$0.08+ by output megapixels",
|
|
1861
|
+
sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/image",
|
|
1862
|
+
defaultOptions: {
|
|
1863
|
+
model: "Standard V2",
|
|
1864
|
+
output_format: "jpeg",
|
|
1865
|
+
upscale_factor: 2
|
|
1866
|
+
}
|
|
1867
|
+
},
|
|
1868
|
+
"topaz-video": {
|
|
1869
|
+
name: "Topaz Video Upscale",
|
|
1870
|
+
endpoint: "fal-ai/topaz/upscale/video",
|
|
1871
|
+
task: "video enhancement",
|
|
1872
|
+
category: "upscale",
|
|
1873
|
+
description: "Professional Topaz video enhancement and upscaling.",
|
|
1874
|
+
inputKind: "video",
|
|
1875
|
+
inputField: "video_url",
|
|
1876
|
+
outputKeys: ["video"],
|
|
1877
|
+
pricing: "$0.01-$0.08/sec by output resolution",
|
|
1878
|
+
sourceUrl: "https://fal.ai/models/fal-ai/topaz/upscale/video",
|
|
1879
|
+
defaultOptions: {
|
|
1880
|
+
model: "Proteus",
|
|
1881
|
+
upscale_factor: 2
|
|
1882
|
+
}
|
|
1883
|
+
},
|
|
1884
|
+
"bria-rmbg": {
|
|
1885
|
+
name: "Bria RMBG 2.0",
|
|
1886
|
+
endpoint: "fal-ai/bria/background/remove",
|
|
1887
|
+
task: "image background removal",
|
|
1888
|
+
category: "background",
|
|
1889
|
+
description: "Commercial-safe background removal for images.",
|
|
1890
|
+
inputKind: "image",
|
|
1891
|
+
inputField: "image_url",
|
|
1892
|
+
outputKeys: ["image"],
|
|
1893
|
+
pricing: "$0.02/image",
|
|
1894
|
+
sourceUrl: "https://fal.ai/models/fal-ai/bria/background/remove"
|
|
1895
|
+
},
|
|
1896
|
+
birefnet: {
|
|
1897
|
+
name: "BirefNet Background Removal",
|
|
1898
|
+
endpoint: "fal-ai/birefnet/v2",
|
|
1899
|
+
task: "image background removal",
|
|
1900
|
+
category: "background",
|
|
1901
|
+
description: "High-resolution dichotomous image segmentation and background removal.",
|
|
1902
|
+
inputKind: "image",
|
|
1903
|
+
inputField: "image_url",
|
|
1904
|
+
outputKeys: ["image", "mask_image"],
|
|
1905
|
+
pricing: "$0/compute-second listed by fal",
|
|
1906
|
+
sourceUrl: "https://fal.ai/models/fal-ai/birefnet/v2",
|
|
1907
|
+
defaultOptions: {
|
|
1908
|
+
model: "General Use (Light)",
|
|
1909
|
+
operating_resolution: "1024x1024",
|
|
1910
|
+
output_format: "png",
|
|
1911
|
+
refine_foreground: true
|
|
1912
|
+
}
|
|
1913
|
+
},
|
|
1914
|
+
rembg: {
|
|
1915
|
+
name: "Remove Background",
|
|
1916
|
+
endpoint: "fal-ai/imageutils/rembg",
|
|
1917
|
+
task: "image background removal",
|
|
1918
|
+
category: "background",
|
|
1919
|
+
description: "Generic image background removal utility.",
|
|
1920
|
+
inputKind: "image",
|
|
1921
|
+
inputField: "image_url",
|
|
1922
|
+
outputKeys: ["image"],
|
|
1923
|
+
pricing: "$0/compute-second listed by fal",
|
|
1924
|
+
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/rembg",
|
|
1925
|
+
defaultOptions: {
|
|
1926
|
+
crop_to_bbox: false
|
|
1927
|
+
}
|
|
1928
|
+
},
|
|
1929
|
+
"bria-video-rmbg": {
|
|
1930
|
+
name: "Bria Video Background Removal",
|
|
1931
|
+
endpoint: "bria/video/background-removal",
|
|
1932
|
+
task: "video background removal",
|
|
1933
|
+
category: "background",
|
|
1934
|
+
description: "Remove video backgrounds with configurable output container.",
|
|
1935
|
+
inputKind: "video",
|
|
1936
|
+
inputField: "video_url",
|
|
1937
|
+
outputKeys: ["video"],
|
|
1938
|
+
pricing: "$0.14/sec",
|
|
1939
|
+
sourceUrl: "https://fal.ai/models/bria/video/background-removal",
|
|
1940
|
+
defaultOptions: {
|
|
1941
|
+
background_color: "Black",
|
|
1942
|
+
output_container_and_codec: "webm_vp9",
|
|
1943
|
+
preserve_audio: true
|
|
1944
|
+
}
|
|
1945
|
+
},
|
|
1946
|
+
lineart: {
|
|
1947
|
+
name: "Line Art Preprocessor",
|
|
1948
|
+
endpoint: "fal-ai/image-preprocessors/lineart",
|
|
1949
|
+
task: "image preprocessing",
|
|
1950
|
+
category: "preprocess",
|
|
1951
|
+
description: "Generate line art/control-style edges from an input image.",
|
|
1952
|
+
inputKind: "image",
|
|
1953
|
+
inputField: "image_url",
|
|
1954
|
+
outputKeys: ["image"],
|
|
1955
|
+
pricing: "$0/compute-second listed by fal",
|
|
1956
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/lineart",
|
|
1957
|
+
defaultOptions: {
|
|
1958
|
+
coarse: false
|
|
1959
|
+
}
|
|
1960
|
+
},
|
|
1961
|
+
"sam-preprocessor": {
|
|
1962
|
+
name: "SAM Preprocessor",
|
|
1963
|
+
endpoint: "fal-ai/image-preprocessors/sam",
|
|
1964
|
+
task: "segmentation preprocessing",
|
|
1965
|
+
category: "preprocess",
|
|
1966
|
+
description: "Generate a SAM segmentation map for ControlNet-style workflows.",
|
|
1967
|
+
inputKind: "image",
|
|
1968
|
+
inputField: "image_url",
|
|
1969
|
+
outputKeys: ["image"],
|
|
1970
|
+
pricing: "$0/compute-second listed by fal",
|
|
1971
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/sam"
|
|
1972
|
+
},
|
|
1973
|
+
"midas-depth": {
|
|
1974
|
+
name: "MiDaS Depth Estimation",
|
|
1975
|
+
endpoint: "fal-ai/imageutils/depth",
|
|
1976
|
+
task: "depth map",
|
|
1977
|
+
category: "depth",
|
|
1978
|
+
description: "Create MiDaS depth maps from input images.",
|
|
1979
|
+
inputKind: "image",
|
|
1980
|
+
inputField: "image_url",
|
|
1981
|
+
outputKeys: ["image"],
|
|
1982
|
+
pricing: "$0/compute-second listed by fal",
|
|
1983
|
+
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/depth",
|
|
1984
|
+
defaultOptions: {
|
|
1985
|
+
a: Math.PI * 2,
|
|
1986
|
+
bg_th: 0.1
|
|
1987
|
+
}
|
|
1988
|
+
},
|
|
1989
|
+
"midas-preprocessor": {
|
|
1990
|
+
name: "MiDaS Preprocessor",
|
|
1991
|
+
endpoint: "fal-ai/image-preprocessors/midas",
|
|
1992
|
+
task: "depth and normal preprocessing",
|
|
1993
|
+
category: "preprocess",
|
|
1994
|
+
description: "Generate MiDaS depth and normal maps for image workflows.",
|
|
1995
|
+
inputKind: "image",
|
|
1996
|
+
inputField: "image_url",
|
|
1997
|
+
outputKeys: ["depth_map", "normal_map"],
|
|
1998
|
+
pricing: "$0/compute-second listed by fal",
|
|
1999
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/midas"
|
|
2000
|
+
},
|
|
2001
|
+
"marigold-depth": {
|
|
2002
|
+
name: "Marigold Depth Estimation",
|
|
2003
|
+
endpoint: "fal-ai/imageutils/marigold-depth",
|
|
2004
|
+
task: "depth map",
|
|
2005
|
+
category: "depth",
|
|
2006
|
+
description: "Create depth maps using Marigold depth estimation.",
|
|
2007
|
+
inputKind: "image",
|
|
2008
|
+
inputField: "image_url",
|
|
2009
|
+
outputKeys: ["image"],
|
|
2010
|
+
pricing: "$0/compute-second listed by fal",
|
|
2011
|
+
sourceUrl: "https://fal.ai/models/fal-ai/imageutils/marigold-depth",
|
|
2012
|
+
defaultOptions: {
|
|
2013
|
+
ensemble_size: 10,
|
|
2014
|
+
num_inference_steps: 10
|
|
2015
|
+
}
|
|
2016
|
+
},
|
|
2017
|
+
"depth-anything": {
|
|
2018
|
+
name: "Depth Anything v2 Preprocessor",
|
|
2019
|
+
endpoint: "fal-ai/image-preprocessors/depth-anything/v2",
|
|
2020
|
+
task: "depth preprocessing",
|
|
2021
|
+
category: "preprocess",
|
|
2022
|
+
description: "Generate Depth Anything v2 depth maps from input images.",
|
|
2023
|
+
inputKind: "image",
|
|
2024
|
+
inputField: "image_url",
|
|
2025
|
+
outputKeys: ["image"],
|
|
2026
|
+
pricing: "$0/compute-second listed by fal",
|
|
2027
|
+
sourceUrl: "https://fal.ai/models/fal-ai/image-preprocessors/depth-anything/v2"
|
|
2028
|
+
},
|
|
2029
|
+
"sam2-auto": {
|
|
2030
|
+
name: "SAM 2 Auto Segment",
|
|
2031
|
+
endpoint: "fal-ai/sam2/auto-segment",
|
|
2032
|
+
task: "automatic image segmentation",
|
|
2033
|
+
category: "segmentation",
|
|
2034
|
+
description: "Automatically segment an image into combined and individual masks.",
|
|
2035
|
+
inputKind: "image",
|
|
2036
|
+
inputField: "image_url",
|
|
2037
|
+
outputKeys: ["combined_mask", "individual_masks"],
|
|
2038
|
+
pricing: "$0/compute-second listed by fal",
|
|
2039
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam2/auto-segment",
|
|
2040
|
+
defaultOptions: {
|
|
2041
|
+
min_mask_region_area: 100,
|
|
2042
|
+
output_format: "png",
|
|
2043
|
+
points_per_side: 32,
|
|
2044
|
+
pred_iou_thresh: 0.88,
|
|
2045
|
+
stability_score_thresh: 0.95
|
|
2046
|
+
}
|
|
2047
|
+
},
|
|
2048
|
+
"sam3-image": {
|
|
2049
|
+
name: "SAM 3 Image",
|
|
2050
|
+
endpoint: "fal-ai/sam-3/image",
|
|
2051
|
+
task: "promptable image segmentation",
|
|
2052
|
+
category: "segmentation",
|
|
2053
|
+
description: "Segment image objects with text, point, or box prompts.",
|
|
2054
|
+
inputKind: "image",
|
|
2055
|
+
inputField: "image_url",
|
|
2056
|
+
outputKeys: ["image", "masks", "metadata", "scores", "boxes"],
|
|
2057
|
+
pricing: "$0.005/request",
|
|
2058
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image",
|
|
2059
|
+
defaultOptions: {
|
|
2060
|
+
apply_mask: true,
|
|
2061
|
+
max_masks: 3,
|
|
2062
|
+
output_format: "png"
|
|
2063
|
+
}
|
|
2064
|
+
},
|
|
2065
|
+
"sam3-image-rle": {
|
|
2066
|
+
name: "SAM 3 Image RLE",
|
|
2067
|
+
endpoint: "fal-ai/sam-3/image-rle",
|
|
2068
|
+
task: "promptable image segmentation to RLE",
|
|
2069
|
+
category: "segmentation",
|
|
2070
|
+
description: "Segment image objects and return run-length encoded masks.",
|
|
2071
|
+
inputKind: "image",
|
|
2072
|
+
inputField: "image_url",
|
|
2073
|
+
outputKeys: ["rle_masks", "metadata", "scores", "boxes"],
|
|
2074
|
+
pricing: "$0.005/request",
|
|
2075
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/image-rle",
|
|
2076
|
+
defaultOptions: {
|
|
2077
|
+
apply_mask: true,
|
|
2078
|
+
max_masks: 3
|
|
2079
|
+
}
|
|
2080
|
+
},
|
|
2081
|
+
"sam3-video": {
|
|
2082
|
+
name: "SAM 3 Video",
|
|
2083
|
+
endpoint: "fal-ai/sam-3/video",
|
|
2084
|
+
task: "promptable video segmentation",
|
|
2085
|
+
category: "segmentation",
|
|
2086
|
+
description: "Segment and track prompted objects across video frames.",
|
|
2087
|
+
inputKind: "video",
|
|
2088
|
+
inputField: "video_url",
|
|
2089
|
+
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2090
|
+
pricing: "$0.005/16 frames",
|
|
2091
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video",
|
|
2092
|
+
defaultOptions: {
|
|
2093
|
+
apply_mask: true,
|
|
2094
|
+
detection_threshold: 0.5,
|
|
2095
|
+
prompt: "person",
|
|
2096
|
+
video_output_type: "X264 (.mp4)"
|
|
2097
|
+
}
|
|
2098
|
+
},
|
|
2099
|
+
"sam3-video-rle": {
|
|
2100
|
+
name: "SAM 3 Video RLE",
|
|
2101
|
+
endpoint: "fal-ai/sam-3/video-rle",
|
|
2102
|
+
task: "promptable video segmentation to RLE",
|
|
2103
|
+
category: "segmentation",
|
|
2104
|
+
description: "Track prompted video objects and return RLE mask data.",
|
|
2105
|
+
inputKind: "video",
|
|
2106
|
+
inputField: "video_url",
|
|
2107
|
+
outputKeys: ["rle_masks", "metadata"],
|
|
2108
|
+
pricing: "$0.005/16 frames",
|
|
2109
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/video-rle",
|
|
2110
|
+
defaultOptions: {
|
|
2111
|
+
apply_mask: true,
|
|
2112
|
+
detection_threshold: 0.5,
|
|
2113
|
+
prompt: "person"
|
|
2114
|
+
}
|
|
2115
|
+
},
|
|
2116
|
+
"sam3-1-video": {
|
|
2117
|
+
name: "SAM 3.1 Video",
|
|
2118
|
+
endpoint: "fal-ai/sam-3-1/video",
|
|
2119
|
+
task: "multi-object video segmentation",
|
|
2120
|
+
category: "segmentation",
|
|
2121
|
+
description: "SAM 3.1 video segmentation with Object Multiplex tracking for multiple objects.",
|
|
2122
|
+
inputKind: "video",
|
|
2123
|
+
inputField: "video_url",
|
|
2124
|
+
outputKeys: ["video", "boundingbox_frames_zip"],
|
|
2125
|
+
pricing: "fal pricing varies by frame count",
|
|
2126
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3-1/video",
|
|
2127
|
+
defaultOptions: {
|
|
2128
|
+
apply_mask: true,
|
|
2129
|
+
prompt: "person"
|
|
2130
|
+
}
|
|
2131
|
+
},
|
|
2132
|
+
"sam3-3d-objects": {
|
|
2133
|
+
name: "SAM 3D Objects",
|
|
2134
|
+
endpoint: "fal-ai/sam-3/3d-objects",
|
|
2135
|
+
task: "single-image 3D object reconstruction",
|
|
2136
|
+
category: "3d",
|
|
2137
|
+
description: "Reconstruct one or more 3D objects from an image and prompts.",
|
|
2138
|
+
inputKind: "image",
|
|
2139
|
+
inputField: "image_url",
|
|
2140
|
+
outputKeys: [
|
|
2141
|
+
"gaussian_splat",
|
|
2142
|
+
"model_glb",
|
|
2143
|
+
"metadata",
|
|
2144
|
+
"individual_splats",
|
|
2145
|
+
"individual_glbs",
|
|
2146
|
+
"artifacts_zip"
|
|
2147
|
+
],
|
|
2148
|
+
pricing: "$0.02/generation",
|
|
2149
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-objects",
|
|
2150
|
+
defaultOptions: {
|
|
2151
|
+
prompt: "car"
|
|
2152
|
+
}
|
|
2153
|
+
},
|
|
2154
|
+
"sam3-3d-body": {
|
|
2155
|
+
name: "SAM 3D Body",
|
|
2156
|
+
endpoint: "fal-ai/sam-3/3d-body",
|
|
2157
|
+
task: "single-image 3D body reconstruction",
|
|
2158
|
+
category: "3d",
|
|
2159
|
+
description: "Reconstruct human body meshes and keypoints from a single image.",
|
|
2160
|
+
inputKind: "image",
|
|
2161
|
+
inputField: "image_url",
|
|
2162
|
+
outputKeys: ["model_glb", "visualization", "meshes", "metadata"],
|
|
2163
|
+
pricing: "$0.015/inference",
|
|
2164
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-body",
|
|
2165
|
+
defaultOptions: {
|
|
2166
|
+
export_meshes: true,
|
|
2167
|
+
include_3d_keypoints: true,
|
|
2168
|
+
include_mhr_params: true
|
|
2169
|
+
}
|
|
2170
|
+
},
|
|
2171
|
+
"sam3-3d-align": {
|
|
2172
|
+
name: "SAM 3D Align",
|
|
2173
|
+
endpoint: "fal-ai/sam-3/3d-align",
|
|
2174
|
+
task: "3D scene alignment",
|
|
2175
|
+
category: "3d",
|
|
2176
|
+
description: "Align SAM 3D objects and bodies into a shared scene.",
|
|
2177
|
+
inputKind: "image",
|
|
2178
|
+
inputField: "image_url",
|
|
2179
|
+
outputKeys: ["scene_glb", "metadata", "artifacts_zip"],
|
|
2180
|
+
pricing: "fal pricing varies by scene",
|
|
2181
|
+
sourceUrl: "https://fal.ai/models/fal-ai/sam-3/3d-align"
|
|
2182
|
+
}
|
|
2183
|
+
};
|
|
2184
|
+
var FAL_TOOLS_CHECKED_AT = FAL_EXPLORE_CHECKED_AT;
|
|
2185
|
+
var FAL_TOOL_IDS = Object.keys(FAL_TOOLS);
|
|
2186
|
+
function isFalToolId(tool) {
|
|
2187
|
+
return Object.hasOwn(FAL_TOOLS, tool);
|
|
2188
|
+
}
|
|
2189
|
+
function buildFalToolRequest(options) {
|
|
2190
|
+
if (!isFalToolId(options.tool)) {
|
|
2191
|
+
throw new Error(`Unknown fal tool: ${options.tool}`);
|
|
2192
|
+
}
|
|
2193
|
+
const tool = FAL_TOOLS[options.tool];
|
|
2194
|
+
const values = options.inputs ?? (options.input ? [options.input] : []);
|
|
2195
|
+
if (values.length === 0) {
|
|
2196
|
+
throw new Error(`${options.tool} requires input media`);
|
|
2197
|
+
}
|
|
2198
|
+
const mediaValue = tool.inputKind === "images" ? values : values[0];
|
|
2199
|
+
const defaultOptions = "defaultOptions" in tool ? tool.defaultOptions : void 0;
|
|
2200
|
+
const body = {
|
|
2201
|
+
...defaultOptions,
|
|
2202
|
+
...options.options,
|
|
2203
|
+
[tool.inputField]: mediaValue
|
|
2204
|
+
};
|
|
2205
|
+
return { endpoint: tool.endpoint, body, tool };
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
// src/server.ts
|
|
2209
|
+
var FAL_BASE_URL = "https://fal.run";
|
|
2210
|
+
var FAL_QUEUE_URL = "https://queue.fal.run";
|
|
2211
|
+
var FAL_REST_URL = "https://rest.alpha.fal.ai";
|
|
2212
|
+
function endpointFromQueueUrl(url, fallback) {
|
|
2213
|
+
if (!url) return fallback;
|
|
2214
|
+
try {
|
|
2215
|
+
const parsed = new URL(url);
|
|
2216
|
+
const match = parsed.pathname.match(/^\/(.+)\/requests\//);
|
|
2217
|
+
return match?.[1] ?? fallback;
|
|
2218
|
+
} catch {
|
|
2219
|
+
return fallback;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
var MotifServer = class {
|
|
2223
|
+
apiKey;
|
|
2224
|
+
timeout;
|
|
2225
|
+
retries;
|
|
2226
|
+
constructor(config) {
|
|
2227
|
+
if (typeof config === "string") {
|
|
2228
|
+
this.apiKey = config;
|
|
2229
|
+
this.timeout = 12e4;
|
|
2230
|
+
this.retries = 3;
|
|
2231
|
+
} else {
|
|
2232
|
+
this.apiKey = config.apiKey;
|
|
2233
|
+
this.timeout = config.timeout ?? 12e4;
|
|
2234
|
+
this.retries = config.retries ?? 3;
|
|
2235
|
+
}
|
|
2236
|
+
if (!this.apiKey) {
|
|
2237
|
+
throw new MotifError("API key is required", 0);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
/** ─── Synchronous Generation ──────────────────────────────── */
|
|
2241
|
+
/** Generate images synchronously (blocks until fal.ai returns). */
|
|
2242
|
+
async generate(options) {
|
|
2243
|
+
const config = MODELS[options.model];
|
|
2244
|
+
if (config?.useQueue) {
|
|
2245
|
+
return this.generateQueued(options);
|
|
2246
|
+
}
|
|
2247
|
+
const { endpoint, body } = buildGenerateBody(options);
|
|
2248
|
+
const response = await this.request(`${FAL_BASE_URL}/${endpoint}`, {
|
|
2249
|
+
method: "POST",
|
|
2250
|
+
body: JSON.stringify(body)
|
|
2251
|
+
});
|
|
2252
|
+
if (response.isErr()) {
|
|
2253
|
+
return err(response.error);
|
|
2254
|
+
}
|
|
2255
|
+
const data = await response.value.json();
|
|
2256
|
+
return this.normalizeResponse(data);
|
|
2257
|
+
}
|
|
2258
|
+
async generateQueued(options) {
|
|
2259
|
+
const job = await this.submitGeneration(options);
|
|
2260
|
+
if (job.isErr()) {
|
|
2261
|
+
return err(job.error);
|
|
2262
|
+
}
|
|
2263
|
+
const pollIntervalMs = 3e3;
|
|
2264
|
+
const maxAttempts = 160;
|
|
2265
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
2266
|
+
const status = await this.getJobStatus(
|
|
2267
|
+
job.value.endpoint,
|
|
2268
|
+
job.value.requestId
|
|
2269
|
+
);
|
|
2270
|
+
if (status.isErr()) {
|
|
2271
|
+
return err(status.error);
|
|
2272
|
+
}
|
|
2273
|
+
if (status.value.status === "completed") {
|
|
2274
|
+
return this.getJobResult(job.value.endpoint, job.value.requestId);
|
|
2275
|
+
}
|
|
2276
|
+
if (status.value.status === "failed") {
|
|
2277
|
+
return err(
|
|
2278
|
+
new MotifError(status.value.error ?? "Queued generation failed", 0)
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
2282
|
+
}
|
|
2283
|
+
return err(new MotifError("Queued generation timed out", 0));
|
|
2284
|
+
}
|
|
2285
|
+
/** ─── Queue-Based Generation ──────────────────────────────── */
|
|
2286
|
+
/** Submit a generation to the fal.ai queue (returns immediately). */
|
|
2287
|
+
async submitGeneration(options) {
|
|
2288
|
+
const { endpoint, body } = buildGenerateBody(options);
|
|
2289
|
+
const response = await this.request(`${FAL_QUEUE_URL}/${endpoint}`, {
|
|
2290
|
+
method: "POST",
|
|
2291
|
+
body: JSON.stringify(body)
|
|
2292
|
+
});
|
|
2293
|
+
if (response.isErr()) {
|
|
2294
|
+
return err(response.error);
|
|
2295
|
+
}
|
|
2296
|
+
const data = await response.value.json();
|
|
2297
|
+
return ok({
|
|
2298
|
+
requestId: data.request_id,
|
|
2299
|
+
endpoint: endpointFromQueueUrl(data.response_url, endpoint),
|
|
2300
|
+
estimatedCost: estimateCost(
|
|
2301
|
+
options.model,
|
|
2302
|
+
options.resolution,
|
|
2303
|
+
options.numImages
|
|
2304
|
+
)
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2307
|
+
/** Check the status of a queued generation. */
|
|
2308
|
+
async getJobStatus(endpoint, requestId) {
|
|
2309
|
+
const url = `${FAL_QUEUE_URL}/${endpoint}/requests/${requestId}/status?logs=1`;
|
|
2310
|
+
const response = await this.request(url);
|
|
2311
|
+
if (response.isErr()) {
|
|
2312
|
+
return err(response.error);
|
|
2313
|
+
}
|
|
2314
|
+
const data = await response.value.json();
|
|
2315
|
+
let status;
|
|
2316
|
+
if (data.status === "IN_QUEUE") {
|
|
2317
|
+
status = "queued";
|
|
2318
|
+
} else if (data.status === "IN_PROGRESS") {
|
|
2319
|
+
status = "processing";
|
|
2320
|
+
} else if (data.status === "COMPLETED") {
|
|
2321
|
+
status = "completed";
|
|
2322
|
+
} else if (data.status === "FAILED" || data.status === "ERROR" || data.status === "CANCELED") {
|
|
2323
|
+
status = "failed";
|
|
2324
|
+
} else {
|
|
2325
|
+
return err(new MotifError(`Unknown job status: ${data.status}`, 0));
|
|
2326
|
+
}
|
|
2327
|
+
return ok({
|
|
2328
|
+
status,
|
|
2329
|
+
error: data.error ?? data.detail,
|
|
2330
|
+
queuePosition: data.queue_position,
|
|
2331
|
+
logs: data.logs
|
|
2332
|
+
});
|
|
2333
|
+
}
|
|
2334
|
+
/** Fetch the completed result from the queue. */
|
|
2335
|
+
async getJobResult(endpoint, requestId) {
|
|
2336
|
+
const url = `${FAL_QUEUE_URL}/${endpoint}/requests/${requestId}`;
|
|
2337
|
+
const response = await this.request(url);
|
|
2338
|
+
if (response.isErr()) {
|
|
2339
|
+
return err(response.error);
|
|
2340
|
+
}
|
|
2341
|
+
const data = await response.value.json();
|
|
2342
|
+
return this.normalizeResponse(data);
|
|
2343
|
+
}
|
|
2344
|
+
/** ─── Processing ──────────────────────────────────────────── */
|
|
2345
|
+
/** Upscale an image using clarity or crystal upscaler. */
|
|
2346
|
+
async upscale(options) {
|
|
2347
|
+
const {
|
|
2348
|
+
imageUrl,
|
|
2349
|
+
model = "clarity",
|
|
2350
|
+
scaleFactor,
|
|
2351
|
+
creativity,
|
|
2352
|
+
resemblance,
|
|
2353
|
+
prompt: upscalePrompt,
|
|
2354
|
+
negativePrompt,
|
|
2355
|
+
numInferenceSteps,
|
|
2356
|
+
guidanceScale
|
|
2357
|
+
} = options;
|
|
2358
|
+
const config = MODELS[model];
|
|
2359
|
+
if (!config || config.type !== "utility") {
|
|
2360
|
+
return err(new MotifError(`Invalid upscale model: ${model}`, 0));
|
|
2361
|
+
}
|
|
2362
|
+
const body = { image_url: imageUrl };
|
|
2363
|
+
if (model === "crystal") {
|
|
2364
|
+
if (scaleFactor !== void 0) body.scale_factor = scaleFactor;
|
|
2365
|
+
if (creativity !== void 0) body.creativity = creativity;
|
|
2366
|
+
} else {
|
|
2367
|
+
if (scaleFactor !== void 0) body.upscale_factor = scaleFactor;
|
|
2368
|
+
if (creativity !== void 0) body.creativity = creativity;
|
|
2369
|
+
if (resemblance !== void 0) body.resemblance = resemblance;
|
|
2370
|
+
if (upscalePrompt) body.prompt = upscalePrompt;
|
|
2371
|
+
if (negativePrompt) body.negative_prompt = negativePrompt;
|
|
2372
|
+
if (numInferenceSteps !== void 0)
|
|
2373
|
+
body.num_inference_steps = numInferenceSteps;
|
|
2374
|
+
if (guidanceScale !== void 0) body.guidance_scale = guidanceScale;
|
|
2375
|
+
}
|
|
2376
|
+
const response = await this.request(`${FAL_BASE_URL}/${config.endpoint}`, {
|
|
2377
|
+
method: "POST",
|
|
2378
|
+
body: JSON.stringify(body)
|
|
2379
|
+
});
|
|
2380
|
+
if (response.isErr()) {
|
|
2381
|
+
return err(response.error);
|
|
2382
|
+
}
|
|
2383
|
+
const data = await response.value.json();
|
|
2384
|
+
return this.normalizeResponse(data);
|
|
2385
|
+
}
|
|
2386
|
+
/** Remove the background from an image. */
|
|
2387
|
+
async removeBackground(options) {
|
|
2388
|
+
const {
|
|
2389
|
+
imageUrl,
|
|
2390
|
+
model = "rmbg",
|
|
2391
|
+
variant,
|
|
2392
|
+
operatingResolution,
|
|
2393
|
+
outputFormat,
|
|
2394
|
+
refineForeground,
|
|
2395
|
+
outputMask
|
|
2396
|
+
} = options;
|
|
2397
|
+
const config = MODELS[model];
|
|
2398
|
+
if (!config) {
|
|
2399
|
+
return err(
|
|
2400
|
+
new MotifError(`Invalid background removal model: ${model}`, 0)
|
|
2401
|
+
);
|
|
2402
|
+
}
|
|
2403
|
+
const rbBody = { image_url: imageUrl };
|
|
2404
|
+
if (model === "rmbg") {
|
|
2405
|
+
if (variant) rbBody.model = variant;
|
|
2406
|
+
if (operatingResolution)
|
|
2407
|
+
rbBody.operating_resolution = operatingResolution;
|
|
2408
|
+
if (outputFormat) rbBody.output_format = outputFormat;
|
|
2409
|
+
if (refineForeground !== void 0)
|
|
2410
|
+
rbBody.refine_foreground = refineForeground;
|
|
2411
|
+
if (outputMask !== void 0) rbBody.output_mask = outputMask;
|
|
2412
|
+
}
|
|
2413
|
+
const response = await this.request(`${FAL_BASE_URL}/${config.endpoint}`, {
|
|
2414
|
+
method: "POST",
|
|
2415
|
+
body: JSON.stringify(rbBody)
|
|
2416
|
+
});
|
|
2417
|
+
if (response.isErr()) {
|
|
2418
|
+
return err(response.error);
|
|
2419
|
+
}
|
|
2420
|
+
const data = await response.value.json();
|
|
2421
|
+
return this.normalizeResponse(data);
|
|
2422
|
+
}
|
|
2423
|
+
/** ─── Video Generation ───────────────────────────────────── */
|
|
2424
|
+
/**
|
|
2425
|
+
* Generate a video from an image using Kling v3 Pro.
|
|
2426
|
+
* This uses the queue API since video generation takes 30-120s.
|
|
2427
|
+
* Returns immediately with a job — poll with getJobStatus/getVideoResult.
|
|
2428
|
+
*/
|
|
2429
|
+
async submitVideo(options) {
|
|
2430
|
+
const {
|
|
2431
|
+
imageUrl,
|
|
2432
|
+
prompt,
|
|
2433
|
+
duration = 5,
|
|
2434
|
+
generateAudio = true,
|
|
2435
|
+
endImageUrl,
|
|
2436
|
+
negativePrompt,
|
|
2437
|
+
cfgScale
|
|
2438
|
+
} = options;
|
|
2439
|
+
const config = MODELS.kling;
|
|
2440
|
+
if (!config) {
|
|
2441
|
+
return err(new MotifError("Kling video model not found", 0));
|
|
2442
|
+
}
|
|
2443
|
+
const body = {
|
|
2444
|
+
start_image_url: imageUrl,
|
|
2445
|
+
prompt,
|
|
2446
|
+
duration: String(duration),
|
|
2447
|
+
generate_audio: generateAudio
|
|
2448
|
+
};
|
|
2449
|
+
if (endImageUrl) {
|
|
2450
|
+
body.end_image_url = endImageUrl;
|
|
2451
|
+
}
|
|
2452
|
+
if (negativePrompt) body.negative_prompt = negativePrompt;
|
|
2453
|
+
if (cfgScale !== void 0) body.cfg_scale = cfgScale;
|
|
2454
|
+
const response = await this.request(`${FAL_QUEUE_URL}/${config.endpoint}`, {
|
|
2455
|
+
method: "POST",
|
|
2456
|
+
body: JSON.stringify(body)
|
|
2457
|
+
});
|
|
2458
|
+
if (response.isErr()) {
|
|
2459
|
+
return err(response.error);
|
|
2460
|
+
}
|
|
2461
|
+
const data = await response.value.json();
|
|
2462
|
+
return ok({
|
|
2463
|
+
requestId: data.request_id,
|
|
2464
|
+
endpoint: endpointFromQueueUrl(data.response_url, config.endpoint),
|
|
2465
|
+
estimatedCost: estimateVideoCost(duration, generateAudio)
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
/** Fetch the completed video result from the queue. */
|
|
2469
|
+
async getVideoResult(endpoint, requestId) {
|
|
2470
|
+
const url = `${FAL_QUEUE_URL}/${endpoint}/requests/${requestId}`;
|
|
2471
|
+
const response = await this.request(url);
|
|
2472
|
+
if (response.isErr()) {
|
|
2473
|
+
return err(response.error);
|
|
2474
|
+
}
|
|
2475
|
+
const data = await response.value.json();
|
|
2476
|
+
if (!data.video) {
|
|
2477
|
+
return err(new MotifError("No video in response", 0));
|
|
2478
|
+
}
|
|
2479
|
+
return ok({
|
|
2480
|
+
url: data.video.url,
|
|
2481
|
+
contentType: data.video.content_type,
|
|
2482
|
+
fileName: data.video.file_name,
|
|
2483
|
+
fileSize: data.video.file_size
|
|
2484
|
+
});
|
|
2485
|
+
}
|
|
2486
|
+
/** ─── File Upload ─────────────────────────────────────────── */
|
|
2487
|
+
/**
|
|
2488
|
+
* Upload a file to fal.ai CDN storage and return the public URL.
|
|
2489
|
+
* Uses the two-step initiate + PUT flow.
|
|
2490
|
+
*/
|
|
2491
|
+
async uploadToFalCdn(file, options) {
|
|
2492
|
+
const initiateResponse = await this.request(
|
|
2493
|
+
`${FAL_REST_URL}/storage/upload/initiate?storage_type=fal-cdn-v3`,
|
|
2494
|
+
{
|
|
2495
|
+
method: "POST",
|
|
2496
|
+
body: JSON.stringify({
|
|
2497
|
+
content_type: options.contentType,
|
|
2498
|
+
file_name: options.fileName
|
|
2499
|
+
})
|
|
2500
|
+
}
|
|
2501
|
+
);
|
|
2502
|
+
if (initiateResponse.isErr()) {
|
|
2503
|
+
return err(initiateResponse.error);
|
|
2504
|
+
}
|
|
2505
|
+
const { file_url, upload_url } = await initiateResponse.value.json();
|
|
2506
|
+
let putResponse;
|
|
2507
|
+
try {
|
|
2508
|
+
const body = Buffer.from(
|
|
2509
|
+
file instanceof Uint8Array ? file : new Uint8Array(file)
|
|
2510
|
+
);
|
|
2511
|
+
putResponse = await fetch(upload_url, {
|
|
2512
|
+
method: "PUT",
|
|
2513
|
+
headers: { "Content-Type": options.contentType },
|
|
2514
|
+
body
|
|
2515
|
+
});
|
|
2516
|
+
} catch (error) {
|
|
2517
|
+
return err(
|
|
2518
|
+
new MotifError(
|
|
2519
|
+
`Upload PUT failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
2520
|
+
0
|
|
2521
|
+
)
|
|
2522
|
+
);
|
|
2523
|
+
}
|
|
2524
|
+
if (!putResponse.ok) {
|
|
2525
|
+
return err(
|
|
2526
|
+
new MotifError(
|
|
2527
|
+
`Upload PUT failed: ${putResponse.status}`,
|
|
2528
|
+
putResponse.status
|
|
2529
|
+
)
|
|
2530
|
+
);
|
|
2531
|
+
}
|
|
2532
|
+
return ok(file_url);
|
|
2533
|
+
}
|
|
2534
|
+
/** ─── Utilities ───────────────────────────────────────────── */
|
|
2535
|
+
/** Run a registered fal utility/tool endpoint. */
|
|
2536
|
+
async runTool(options) {
|
|
2537
|
+
let request;
|
|
2538
|
+
try {
|
|
2539
|
+
request = buildFalToolRequest(options);
|
|
2540
|
+
} catch (error) {
|
|
2541
|
+
return err(
|
|
2542
|
+
new MotifError(
|
|
2543
|
+
error instanceof Error ? error.message : String(error),
|
|
2544
|
+
0
|
|
2545
|
+
)
|
|
2546
|
+
);
|
|
2547
|
+
}
|
|
2548
|
+
const response = await this.request(`${FAL_BASE_URL}/${request.endpoint}`, {
|
|
2549
|
+
method: "POST",
|
|
2550
|
+
body: JSON.stringify(request.body)
|
|
2551
|
+
});
|
|
2552
|
+
if (response.isErr()) {
|
|
2553
|
+
return err(response.error);
|
|
2554
|
+
}
|
|
2555
|
+
return ok(await response.value.json());
|
|
2556
|
+
}
|
|
2557
|
+
/** Estimate cost for a generation (no API call). */
|
|
2558
|
+
estimateCost(model, resolution, numImages) {
|
|
2559
|
+
return estimateCost(model, resolution, numImages);
|
|
2560
|
+
}
|
|
2561
|
+
/** Build the fal.ai request body without sending it. */
|
|
2562
|
+
buildRequestBody(options) {
|
|
2563
|
+
return buildGenerateBody(options);
|
|
2564
|
+
}
|
|
2565
|
+
/** Model registry. */
|
|
2566
|
+
get models() {
|
|
2567
|
+
return MODELS;
|
|
2568
|
+
}
|
|
2569
|
+
/** Generation model keys. */
|
|
2570
|
+
get generationModels() {
|
|
2571
|
+
return GENERATION_MODELS;
|
|
2572
|
+
}
|
|
2573
|
+
/** Utility model keys. */
|
|
2574
|
+
get utilityModels() {
|
|
2575
|
+
return UTILITY_MODELS;
|
|
2576
|
+
}
|
|
2577
|
+
/** Registered fal utility/tool endpoints. */
|
|
2578
|
+
get tools() {
|
|
2579
|
+
return FAL_TOOLS;
|
|
2580
|
+
}
|
|
2581
|
+
/** ─── Private ─────────────────────────────────────────────── */
|
|
2582
|
+
/** Authenticated fetch to fal.ai APIs with retry logic. */
|
|
2583
|
+
async request(url, options = {}) {
|
|
2584
|
+
let lastError = null;
|
|
2585
|
+
for (let attempt = 0; attempt <= this.retries; attempt++) {
|
|
2586
|
+
const controller = new AbortController();
|
|
2587
|
+
const timeoutId = setTimeout(() => controller.abort(), this.timeout);
|
|
2588
|
+
try {
|
|
2589
|
+
const response = await fetch(url, {
|
|
2590
|
+
...options,
|
|
2591
|
+
signal: controller.signal,
|
|
2592
|
+
headers: {
|
|
2593
|
+
Authorization: `Key ${this.apiKey}`,
|
|
2594
|
+
"Content-Type": "application/json",
|
|
2595
|
+
...options.headers
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
clearTimeout(timeoutId);
|
|
2599
|
+
if ((response.status === 429 || response.status >= 500) && attempt < this.retries) {
|
|
2600
|
+
const delay = 1e3 * 2 ** attempt;
|
|
2601
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
2602
|
+
continue;
|
|
2603
|
+
}
|
|
2604
|
+
if (!response.ok) {
|
|
2605
|
+
const text = await response.text();
|
|
2606
|
+
return err(
|
|
2607
|
+
new MotifError(
|
|
2608
|
+
`Request failed: ${response.status} ${text}`,
|
|
2609
|
+
response.status
|
|
2610
|
+
)
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
return ok(response);
|
|
2614
|
+
} catch (error) {
|
|
2615
|
+
clearTimeout(timeoutId);
|
|
2616
|
+
if (error instanceof MotifError) {
|
|
2617
|
+
return err(error);
|
|
2618
|
+
}
|
|
2619
|
+
lastError = new MotifError(
|
|
2620
|
+
error instanceof Error ? error.message : String(error),
|
|
2621
|
+
0
|
|
2622
|
+
);
|
|
2623
|
+
if (attempt < this.retries) {
|
|
2624
|
+
const delay = 1e3 * 2 ** attempt;
|
|
2625
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
2626
|
+
}
|
|
2627
|
+
}
|
|
2628
|
+
}
|
|
2629
|
+
return err(lastError ?? new MotifError("Request failed after retries", 0));
|
|
2630
|
+
}
|
|
2631
|
+
/**
|
|
2632
|
+
* Normalize fal.ai responses.
|
|
2633
|
+
* Some APIs return `{ image: {...} }` instead of `{ images: [...] }`.
|
|
2634
|
+
*/
|
|
2635
|
+
normalizeResponse(data) {
|
|
2636
|
+
const obj = data;
|
|
2637
|
+
if ("detail" in obj) {
|
|
2638
|
+
return err(
|
|
2639
|
+
new MotifError(obj.detail, 0, "FAL_ERROR")
|
|
2640
|
+
);
|
|
2641
|
+
}
|
|
2642
|
+
if ("image" in obj && !("images" in obj)) {
|
|
2643
|
+
return ok({
|
|
2644
|
+
images: [obj.image],
|
|
2645
|
+
seed: obj.seed,
|
|
2646
|
+
prompt: obj.prompt
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
return ok(obj);
|
|
2650
|
+
}
|
|
2651
|
+
};
|
|
2652
|
+
var MotifError = class extends Error {
|
|
2653
|
+
status;
|
|
2654
|
+
code;
|
|
2655
|
+
constructor(message, status, code) {
|
|
2656
|
+
super(message);
|
|
2657
|
+
this.name = "MotifError";
|
|
2658
|
+
this.status = status;
|
|
2659
|
+
this.code = code;
|
|
2660
|
+
}
|
|
2661
|
+
};
|
|
2662
|
+
export {
|
|
2663
|
+
ASPECT_RATIOS,
|
|
2664
|
+
FAL_TOOLS,
|
|
2665
|
+
FAL_TOOLS_CHECKED_AT,
|
|
2666
|
+
FAL_TOOL_IDS,
|
|
2667
|
+
FORMAT_PRESETS,
|
|
2668
|
+
GENERATION_MODELS,
|
|
2669
|
+
IDEOGRAM_STYLES,
|
|
2670
|
+
IMAGE_EDITING_TOP_20,
|
|
2671
|
+
IMAGE_TEXT_TO_IMAGE_TOP_20,
|
|
2672
|
+
MODELS,
|
|
2673
|
+
MotifError,
|
|
2674
|
+
MotifServer,
|
|
2675
|
+
RECRAFT_STYLES,
|
|
2676
|
+
RESOLUTIONS,
|
|
2677
|
+
UTILITY_MODELS,
|
|
2678
|
+
VIDEO_IMAGE_TO_VIDEO_TOP_15,
|
|
2679
|
+
VIDEO_MODELS,
|
|
2680
|
+
VIDEO_TEXT_TO_VIDEO_TOP_15,
|
|
2681
|
+
aspectToFalImageSize,
|
|
2682
|
+
aspectToGptSize,
|
|
2683
|
+
buildFalToolRequest,
|
|
2684
|
+
buildGenerateBody,
|
|
2685
|
+
err2 as err,
|
|
2686
|
+
estimateCost,
|
|
2687
|
+
estimateVideoCost,
|
|
2688
|
+
getFalKeyFromEnv,
|
|
2689
|
+
isFalToolId,
|
|
2690
|
+
motifEnvSchema,
|
|
2691
|
+
ok2 as ok,
|
|
2692
|
+
parseMotifEnv
|
|
2693
|
+
};
|