@howells/motif-sdk 0.2.0 → 0.3.1
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/README.md +86 -6
- package/dist/index.cjs +171 -5
- package/dist/index.d.cts +145 -1
- package/dist/index.d.ts +145 -1
- package/dist/index.js +165 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,16 +8,22 @@ Public Node SDK for Motif fal.ai generation, editing, utility tools, and model m
|
|
|
8
8
|
npm install @howells/motif-sdk
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Generate Images
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import { MotifServer
|
|
14
|
+
import { MotifServer } from "@howells/motif-sdk";
|
|
15
15
|
|
|
16
|
-
const motif = new MotifServer(
|
|
16
|
+
const motif = new MotifServer({
|
|
17
|
+
apiKey: process.env.FAL_KEY!,
|
|
18
|
+
retries: 3,
|
|
19
|
+
timeout: 120_000,
|
|
20
|
+
});
|
|
17
21
|
|
|
18
22
|
const result = await motif.generate({
|
|
19
23
|
model: "banana2",
|
|
20
24
|
prompt: "editorial product photo",
|
|
25
|
+
resolution: "2K",
|
|
26
|
+
enableGoogleSearch: true,
|
|
21
27
|
ephemeral: true,
|
|
22
28
|
});
|
|
23
29
|
|
|
@@ -28,13 +34,88 @@ if (result.isErr()) {
|
|
|
28
34
|
console.log(result.value.images[0]?.url);
|
|
29
35
|
```
|
|
30
36
|
|
|
37
|
+
Every async SDK method returns `Result<T, MotifError>` from `neverthrow`. Methods do not throw for fal request failures; check `isErr()` / `isOk()`.
|
|
38
|
+
|
|
39
|
+
## Dry-Run Request Bodies
|
|
40
|
+
|
|
41
|
+
Use `buildGenerateBody` or `motif.buildRequestBody()` when you need the exact fal endpoint and request body without making an API call.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { buildGenerateBody } from "@howells/motif-sdk";
|
|
45
|
+
|
|
46
|
+
const preview = buildGenerateBody({
|
|
47
|
+
model: "gpt2",
|
|
48
|
+
prompt: "change the wall color",
|
|
49
|
+
editImageUrls: ["https://example.com/interior.png"],
|
|
50
|
+
imageSize: "1536x1024",
|
|
51
|
+
maskImageUrl: "https://example.com/wall-mask.png",
|
|
52
|
+
quality: "auto",
|
|
53
|
+
syncMode: true,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(preview.endpoint);
|
|
57
|
+
console.log(preview.body);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Queue, Upload, and Cleanup
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const job = await motif.submitGeneration({
|
|
64
|
+
model: "gpt2",
|
|
65
|
+
prompt: "gallery poster",
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (job.isOk()) {
|
|
69
|
+
const status = await motif.getJobStatus(job.value.endpoint, job.value.requestId);
|
|
70
|
+
const completed = await motif.getJobResult(job.value.endpoint, job.value.requestId);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const uploaded = await motif.uploadToFalCdn(fileBytes, {
|
|
74
|
+
contentType: "image/png",
|
|
75
|
+
fileName: "reference.png",
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const deleted = await motif.deletePayloads("fal-request-id");
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Utility Tools and Video
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
const mask = await motif.runTool({
|
|
85
|
+
tool: "sam3-image",
|
|
86
|
+
input: "https://example.com/input.png",
|
|
87
|
+
options: { prompt: "shoe", max_masks: 2 },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const videoJob = await motif.submitVideo({
|
|
91
|
+
imageUrl: "https://example.com/frame.png",
|
|
92
|
+
prompt: "slow cinematic push-in",
|
|
93
|
+
duration: 5,
|
|
94
|
+
generateAudio: false,
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
31
98
|
## Main Exports
|
|
32
99
|
|
|
33
100
|
- `MotifServer` - Result-returning fal client for generation, queue jobs, upload, utility tools, and payload deletion.
|
|
34
101
|
- `buildGenerateBody` - Pure fal request normalization for dry runs and tests.
|
|
35
|
-
- `MODELS` - Motif model aliases, fal endpoints, capabilities, pricing, and benchmarks.
|
|
36
|
-
- `FAL_TOOLS` - Normalized fal utility endpoints such as SAM, depth, upscaling, moderation, and background removal.
|
|
102
|
+
- `MODELS`, `GENERATION_MODELS`, `UTILITY_MODELS`, `VIDEO_MODELS` - Motif model aliases, fal endpoints, capabilities, pricing, and benchmarks.
|
|
103
|
+
- `FAL_TOOLS`, `FAL_TOOL_IDS`, `buildFalToolRequest`, `isFalToolId` - Normalized fal utility endpoints such as SAM, depth, upscaling, moderation, and background removal.
|
|
104
|
+
- `ASPECT_RATIOS`, `RESOLUTIONS`, `FORMAT_PRESETS`, `aspectToGptSize`, `aspectToFalImageSize` - Shared sizing metadata and normalization helpers.
|
|
105
|
+
- `IMAGE_TEXT_TO_IMAGE_TOP_20`, `IMAGE_EDITING_TOP_20`, `VIDEO_TEXT_TO_VIDEO_TOP_15`, `VIDEO_IMAGE_TO_VIDEO_TOP_15` - Bundled Artificial Analysis snapshots.
|
|
106
|
+
- `estimateCost`, `estimateVideoCost` - Local cost estimates used by CLI dry runs and SDK previews.
|
|
37
107
|
- `getFalKeyFromEnv` - `@howells/envy` backed `FAL_KEY` parsing.
|
|
108
|
+
- Re-exported `neverthrow` helpers: `ok`, `err`, `Result`, `ResultAsync`.
|
|
109
|
+
|
|
110
|
+
## Common Types
|
|
111
|
+
|
|
112
|
+
The package exports public types for generation, processing, queue, metadata, and utility tools:
|
|
113
|
+
|
|
114
|
+
- `GenerateOptions`, `MotifResponse`, `MotifImage`
|
|
115
|
+
- `UpscaleOptions`, `RemoveBackgroundOptions`, `VideoOptions`, `VideoResponse`
|
|
116
|
+
- `QueuedJob`, `JobStatus`, `MotifServerConfig`
|
|
117
|
+
- `ModelConfig`, `AspectRatio`, `Resolution`, `ImageSize`, `ImageQuality`, `BackgroundMode`, `ThinkingLevel`
|
|
118
|
+
- `FalToolConfig`, `FalToolId`, `FalToolRequest`, `FalToolRunOptions`
|
|
38
119
|
|
|
39
120
|
## Testing
|
|
40
121
|
|
|
@@ -48,4 +129,3 @@ Live fal canaries are opt-in:
|
|
|
48
129
|
```bash
|
|
49
130
|
RUN_FAL_CANARY=1 pnpm --filter @howells/motif-sdk test -- tests/fal-canary.test.ts
|
|
50
131
|
```
|
|
51
|
-
|
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
ASPECT_RATIOS: () => ASPECT_RATIOS,
|
|
24
|
+
CREATIVE_FIELDS: () => CREATIVE_FIELDS,
|
|
25
|
+
CREATIVE_TAXONOMY: () => CREATIVE_TAXONOMY,
|
|
26
|
+
CreativeOptionError: () => CreativeOptionError,
|
|
24
27
|
FAL_TOOLS: () => FAL_TOOLS,
|
|
25
28
|
FAL_TOOLS_CHECKED_AT: () => FAL_TOOLS_CHECKED_AT,
|
|
26
29
|
FAL_TOOL_IDS: () => FAL_TOOL_IDS,
|
|
@@ -42,6 +45,7 @@ __export(index_exports, {
|
|
|
42
45
|
aspectToGptSize: () => aspectToGptSize,
|
|
43
46
|
buildFalToolRequest: () => buildFalToolRequest,
|
|
44
47
|
buildGenerateBody: () => buildGenerateBody,
|
|
48
|
+
enrichPrompt: () => enrichPrompt,
|
|
45
49
|
err: () => import_neverthrow2.err,
|
|
46
50
|
estimateCost: () => estimateCost,
|
|
47
51
|
estimateVideoCost: () => estimateVideoCost,
|
|
@@ -49,7 +53,8 @@ __export(index_exports, {
|
|
|
49
53
|
isFalToolId: () => isFalToolId,
|
|
50
54
|
motifEnvSchema: () => motifEnvSchema,
|
|
51
55
|
ok: () => import_neverthrow2.ok,
|
|
52
|
-
parseMotifEnv: () => parseMotifEnv
|
|
56
|
+
parseMotifEnv: () => parseMotifEnv,
|
|
57
|
+
sanitizePrompt: () => sanitizePrompt
|
|
53
58
|
});
|
|
54
59
|
module.exports = __toCommonJS(index_exports);
|
|
55
60
|
var import_neverthrow2 = require("neverthrow");
|
|
@@ -936,6 +941,10 @@ function estimateCost(model, resolution, numImages = 1) {
|
|
|
936
941
|
if ((model === "banana" || model === "gemini3") && resolution === "4K") {
|
|
937
942
|
return configuredPrice * 2 * numImages;
|
|
938
943
|
}
|
|
944
|
+
if (model === "banana2") {
|
|
945
|
+
const multiplier = resolution === "4K" ? 2 : resolution === "2K" ? 1.5 : resolution === "0.5K" ? 0.75 : 1;
|
|
946
|
+
return configuredPrice * multiplier * numImages;
|
|
947
|
+
}
|
|
939
948
|
return configuredPrice * numImages;
|
|
940
949
|
}
|
|
941
950
|
switch (model) {
|
|
@@ -969,6 +978,136 @@ function estimateVideoCost(durationSeconds = 5, generateAudio = true) {
|
|
|
969
978
|
return perSecond * durationSeconds;
|
|
970
979
|
}
|
|
971
980
|
|
|
981
|
+
// src/creative.ts
|
|
982
|
+
var CONTROL_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
|
|
983
|
+
var CreativeOptionError = class extends Error {
|
|
984
|
+
availableIds;
|
|
985
|
+
code = "INVALID_OPTION";
|
|
986
|
+
field;
|
|
987
|
+
value;
|
|
988
|
+
constructor(details) {
|
|
989
|
+
super(
|
|
990
|
+
`Unknown creative ${details.field}: ${details.value}. Available: ${details.availableIds.join(", ")}`
|
|
991
|
+
);
|
|
992
|
+
this.name = "CreativeOptionError";
|
|
993
|
+
this.availableIds = details.availableIds;
|
|
994
|
+
this.field = details.field;
|
|
995
|
+
this.value = details.value;
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
var CREATIVE_FIELDS = [
|
|
999
|
+
"recipe",
|
|
1000
|
+
"shot",
|
|
1001
|
+
"lighting",
|
|
1002
|
+
"genre",
|
|
1003
|
+
"camera",
|
|
1004
|
+
"color",
|
|
1005
|
+
"material",
|
|
1006
|
+
"motion"
|
|
1007
|
+
];
|
|
1008
|
+
var CREATIVE_TAXONOMY = {
|
|
1009
|
+
recipe: [
|
|
1010
|
+
{
|
|
1011
|
+
clause: "cinematic scene",
|
|
1012
|
+
description: "Frames the prompt as a cinematic still or scene.",
|
|
1013
|
+
id: "cinematic",
|
|
1014
|
+
label: "Cinematic"
|
|
1015
|
+
}
|
|
1016
|
+
],
|
|
1017
|
+
shot: [
|
|
1018
|
+
{
|
|
1019
|
+
clause: "close-up composition with controlled depth of field",
|
|
1020
|
+
description: "Tight framing that emphasizes subject detail.",
|
|
1021
|
+
id: "close-up",
|
|
1022
|
+
label: "Close-up"
|
|
1023
|
+
}
|
|
1024
|
+
],
|
|
1025
|
+
lighting: [
|
|
1026
|
+
{
|
|
1027
|
+
clause: "rim lighting with defined edge highlights",
|
|
1028
|
+
description: "Back or side light that separates the subject edge.",
|
|
1029
|
+
id: "rim",
|
|
1030
|
+
label: "Rim"
|
|
1031
|
+
}
|
|
1032
|
+
],
|
|
1033
|
+
genre: [
|
|
1034
|
+
{
|
|
1035
|
+
clause: "film noir mood with high contrast shadows",
|
|
1036
|
+
description: "High-contrast cinematic mood with shadow-forward drama.",
|
|
1037
|
+
id: "film-noir",
|
|
1038
|
+
label: "Film noir"
|
|
1039
|
+
}
|
|
1040
|
+
],
|
|
1041
|
+
camera: [
|
|
1042
|
+
{
|
|
1043
|
+
clause: "macro product photography with crisp surface detail",
|
|
1044
|
+
description: "Product-oriented macro camera language for close detail.",
|
|
1045
|
+
id: "macro-product",
|
|
1046
|
+
label: "Macro product"
|
|
1047
|
+
}
|
|
1048
|
+
],
|
|
1049
|
+
color: [
|
|
1050
|
+
{
|
|
1051
|
+
clause: "monochrome palette with tonal contrast",
|
|
1052
|
+
description: "Black-and-white or single-channel tonal treatment.",
|
|
1053
|
+
id: "monochrome",
|
|
1054
|
+
label: "Monochrome"
|
|
1055
|
+
}
|
|
1056
|
+
],
|
|
1057
|
+
material: [
|
|
1058
|
+
{
|
|
1059
|
+
clause: "reflective material surfaces with controlled highlights",
|
|
1060
|
+
description: "Emphasizes reflections and highlight control on surfaces.",
|
|
1061
|
+
id: "reflective",
|
|
1062
|
+
label: "Reflective"
|
|
1063
|
+
}
|
|
1064
|
+
],
|
|
1065
|
+
motion: [
|
|
1066
|
+
{
|
|
1067
|
+
clause: "still composition with no motion blur",
|
|
1068
|
+
description: "Freezes the subject without implied movement.",
|
|
1069
|
+
id: "still",
|
|
1070
|
+
label: "Still"
|
|
1071
|
+
}
|
|
1072
|
+
]
|
|
1073
|
+
};
|
|
1074
|
+
function sanitizePrompt(prompt) {
|
|
1075
|
+
return prompt.replace(CONTROL_CHAR_REGEX, "").replace(/\r\n/g, "\n").trim();
|
|
1076
|
+
}
|
|
1077
|
+
function enrichPrompt(options) {
|
|
1078
|
+
const basePrompt = sanitizePrompt(options.prompt);
|
|
1079
|
+
const clauses = [];
|
|
1080
|
+
const selected = {};
|
|
1081
|
+
for (const field of CREATIVE_FIELDS) {
|
|
1082
|
+
const optionId = options.creative?.[field];
|
|
1083
|
+
if (!optionId) {
|
|
1084
|
+
continue;
|
|
1085
|
+
}
|
|
1086
|
+
const option = CREATIVE_TAXONOMY[field].find(
|
|
1087
|
+
(candidate) => candidate.id === optionId
|
|
1088
|
+
);
|
|
1089
|
+
if (!option) {
|
|
1090
|
+
throw new CreativeOptionError({
|
|
1091
|
+
availableIds: CREATIVE_TAXONOMY[field].map((candidate) => candidate.id),
|
|
1092
|
+
field,
|
|
1093
|
+
value: optionId
|
|
1094
|
+
});
|
|
1095
|
+
}
|
|
1096
|
+
selected[field] = option.id;
|
|
1097
|
+
if (!clauses.includes(option.clause)) {
|
|
1098
|
+
clauses.push(option.clause);
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
return {
|
|
1102
|
+
basePrompt,
|
|
1103
|
+
creative: {
|
|
1104
|
+
clauses,
|
|
1105
|
+
selected
|
|
1106
|
+
},
|
|
1107
|
+
prompt: clauses.length ? [basePrompt, ...clauses].join(", ") : basePrompt
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
|
|
972
1111
|
// src/env.ts
|
|
973
1112
|
var import_envy = require("@howells/envy");
|
|
974
1113
|
var import_zod = require("zod");
|
|
@@ -1135,6 +1274,7 @@ function buildGenerateBody(options) {
|
|
|
1135
1274
|
const {
|
|
1136
1275
|
prompt,
|
|
1137
1276
|
model,
|
|
1277
|
+
creative,
|
|
1138
1278
|
aspect = "1:1",
|
|
1139
1279
|
resolution = "2K",
|
|
1140
1280
|
numImages = 1,
|
|
@@ -1169,7 +1309,8 @@ function buildGenerateBody(options) {
|
|
|
1169
1309
|
throw new Error(`Unknown model: ${model}`);
|
|
1170
1310
|
}
|
|
1171
1311
|
let endpoint = config.endpoint;
|
|
1172
|
-
const
|
|
1312
|
+
const enrichedPrompt = creative ? enrichPrompt({ prompt, creative }).prompt : prompt;
|
|
1313
|
+
const body = { prompt: enrichedPrompt };
|
|
1173
1314
|
const hasEditImages = Boolean(editImageUrls?.length);
|
|
1174
1315
|
const editImages = editImageUrls ?? [];
|
|
1175
1316
|
const explicitAspect = options.aspect !== void 0;
|
|
@@ -2298,7 +2439,13 @@ var MotifServer = class {
|
|
|
2298
2439
|
if (config?.useQueue) {
|
|
2299
2440
|
return this.generateQueued(options);
|
|
2300
2441
|
}
|
|
2301
|
-
|
|
2442
|
+
let built;
|
|
2443
|
+
try {
|
|
2444
|
+
built = buildGenerateBody(options);
|
|
2445
|
+
} catch (error) {
|
|
2446
|
+
return (0, import_neverthrow.err)(toMotifError(error));
|
|
2447
|
+
}
|
|
2448
|
+
const { endpoint, body } = built;
|
|
2302
2449
|
const response = await this.request(`${FAL_BASE_URL}/${endpoint}`, {
|
|
2303
2450
|
method: "POST",
|
|
2304
2451
|
body: JSON.stringify(body),
|
|
@@ -2340,7 +2487,13 @@ var MotifServer = class {
|
|
|
2340
2487
|
/** ─── Queue-Based Generation ──────────────────────────────── */
|
|
2341
2488
|
/** Submit a generation to the fal.ai queue (returns immediately). */
|
|
2342
2489
|
async submitGeneration(options) {
|
|
2343
|
-
|
|
2490
|
+
let built;
|
|
2491
|
+
try {
|
|
2492
|
+
built = buildGenerateBody(options);
|
|
2493
|
+
} catch (error) {
|
|
2494
|
+
return (0, import_neverthrow.err)(toMotifError(error));
|
|
2495
|
+
}
|
|
2496
|
+
const { endpoint, body } = built;
|
|
2344
2497
|
const response = await this.request(`${FAL_QUEUE_URL}/${endpoint}`, {
|
|
2345
2498
|
method: "POST",
|
|
2346
2499
|
body: JSON.stringify(body),
|
|
@@ -2740,9 +2893,20 @@ var MotifError = class extends Error {
|
|
|
2740
2893
|
this.code = code;
|
|
2741
2894
|
}
|
|
2742
2895
|
};
|
|
2896
|
+
function toMotifError(error) {
|
|
2897
|
+
if (error instanceof MotifError) {
|
|
2898
|
+
return error;
|
|
2899
|
+
}
|
|
2900
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2901
|
+
const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2902
|
+
return new MotifError(message, 0, code);
|
|
2903
|
+
}
|
|
2743
2904
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2744
2905
|
0 && (module.exports = {
|
|
2745
2906
|
ASPECT_RATIOS,
|
|
2907
|
+
CREATIVE_FIELDS,
|
|
2908
|
+
CREATIVE_TAXONOMY,
|
|
2909
|
+
CreativeOptionError,
|
|
2746
2910
|
FAL_TOOLS,
|
|
2747
2911
|
FAL_TOOLS_CHECKED_AT,
|
|
2748
2912
|
FAL_TOOL_IDS,
|
|
@@ -2764,6 +2928,7 @@ var MotifError = class extends Error {
|
|
|
2764
2928
|
aspectToGptSize,
|
|
2765
2929
|
buildFalToolRequest,
|
|
2766
2930
|
buildGenerateBody,
|
|
2931
|
+
enrichPrompt,
|
|
2767
2932
|
err,
|
|
2768
2933
|
estimateCost,
|
|
2769
2934
|
estimateVideoCost,
|
|
@@ -2771,5 +2936,6 @@ var MotifError = class extends Error {
|
|
|
2771
2936
|
isFalToolId,
|
|
2772
2937
|
motifEnvSchema,
|
|
2773
2938
|
ok,
|
|
2774
|
-
parseMotifEnv
|
|
2939
|
+
parseMotifEnv,
|
|
2940
|
+
sanitizePrompt
|
|
2775
2941
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -3,6 +3,143 @@ export { Result, ResultAsync, err, ok } from 'neverthrow';
|
|
|
3
3
|
import * as _howells_envy from '@howells/envy';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Creative direction fields applied to prompts in Motif's canonical order.
|
|
8
|
+
*
|
|
9
|
+
* The order matters when multiple fields are selected because prompt clauses
|
|
10
|
+
* are appended in this sequence for stable dry runs, tests, and history.
|
|
11
|
+
*/
|
|
12
|
+
type CreativeField = "recipe" | "shot" | "lighting" | "genre" | "camera" | "color" | "material" | "motion";
|
|
13
|
+
/**
|
|
14
|
+
* Selected creative option ids keyed by direction field.
|
|
15
|
+
*
|
|
16
|
+
* Values must match option ids from `CREATIVE_TAXONOMY`; unknown ids throw a
|
|
17
|
+
* `CreativeOptionError` before any fal request body is built.
|
|
18
|
+
*/
|
|
19
|
+
type CreativeDirection = Partial<Record<CreativeField, string>>;
|
|
20
|
+
/** A single selectable creative direction option exposed to CLI and MCP schemas. */
|
|
21
|
+
interface CreativeOption {
|
|
22
|
+
/** Prompt fragment appended when this option is selected. */
|
|
23
|
+
clause: string;
|
|
24
|
+
/** Human-facing explanation used in schema metadata and generated docs. */
|
|
25
|
+
description: string;
|
|
26
|
+
/** Stable machine id accepted by `CreativeDirection`. */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Short display label for UIs and schema enum descriptions. */
|
|
29
|
+
label: string;
|
|
30
|
+
}
|
|
31
|
+
/** Structured details returned when a creative direction id is not recognized. */
|
|
32
|
+
interface CreativeOptionErrorDetails {
|
|
33
|
+
availableIds: string[];
|
|
34
|
+
code: "INVALID_OPTION";
|
|
35
|
+
field: CreativeField;
|
|
36
|
+
value: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when prompt enrichment receives an unknown creative option id.
|
|
40
|
+
*
|
|
41
|
+
* The extra fields make CLI and agent callers able to show field-specific
|
|
42
|
+
* recovery hints without parsing the error message.
|
|
43
|
+
*/
|
|
44
|
+
declare class CreativeOptionError extends Error implements CreativeOptionErrorDetails {
|
|
45
|
+
readonly availableIds: string[];
|
|
46
|
+
readonly code = "INVALID_OPTION";
|
|
47
|
+
readonly field: CreativeField;
|
|
48
|
+
readonly value: string;
|
|
49
|
+
constructor(details: Omit<CreativeOptionErrorDetails, "code">);
|
|
50
|
+
}
|
|
51
|
+
/** Canonical creative field order used for prompt enrichment and schema output. */
|
|
52
|
+
declare const CREATIVE_FIELDS: readonly ["recipe", "shot", "lighting", "genre", "camera", "color", "material", "motion"];
|
|
53
|
+
/**
|
|
54
|
+
* Built-in creative direction catalog.
|
|
55
|
+
*
|
|
56
|
+
* Each field contains the option ids accepted by `CreativeDirection` and the
|
|
57
|
+
* exact prompt clause that will be appended when selected.
|
|
58
|
+
*/
|
|
59
|
+
declare const CREATIVE_TAXONOMY: {
|
|
60
|
+
readonly recipe: readonly [{
|
|
61
|
+
readonly clause: "cinematic scene";
|
|
62
|
+
readonly description: "Frames the prompt as a cinematic still or scene.";
|
|
63
|
+
readonly id: "cinematic";
|
|
64
|
+
readonly label: "Cinematic";
|
|
65
|
+
}];
|
|
66
|
+
readonly shot: readonly [{
|
|
67
|
+
readonly clause: "close-up composition with controlled depth of field";
|
|
68
|
+
readonly description: "Tight framing that emphasizes subject detail.";
|
|
69
|
+
readonly id: "close-up";
|
|
70
|
+
readonly label: "Close-up";
|
|
71
|
+
}];
|
|
72
|
+
readonly lighting: readonly [{
|
|
73
|
+
readonly clause: "rim lighting with defined edge highlights";
|
|
74
|
+
readonly description: "Back or side light that separates the subject edge.";
|
|
75
|
+
readonly id: "rim";
|
|
76
|
+
readonly label: "Rim";
|
|
77
|
+
}];
|
|
78
|
+
readonly genre: readonly [{
|
|
79
|
+
readonly clause: "film noir mood with high contrast shadows";
|
|
80
|
+
readonly description: "High-contrast cinematic mood with shadow-forward drama.";
|
|
81
|
+
readonly id: "film-noir";
|
|
82
|
+
readonly label: "Film noir";
|
|
83
|
+
}];
|
|
84
|
+
readonly camera: readonly [{
|
|
85
|
+
readonly clause: "macro product photography with crisp surface detail";
|
|
86
|
+
readonly description: "Product-oriented macro camera language for close detail.";
|
|
87
|
+
readonly id: "macro-product";
|
|
88
|
+
readonly label: "Macro product";
|
|
89
|
+
}];
|
|
90
|
+
readonly color: readonly [{
|
|
91
|
+
readonly clause: "monochrome palette with tonal contrast";
|
|
92
|
+
readonly description: "Black-and-white or single-channel tonal treatment.";
|
|
93
|
+
readonly id: "monochrome";
|
|
94
|
+
readonly label: "Monochrome";
|
|
95
|
+
}];
|
|
96
|
+
readonly material: readonly [{
|
|
97
|
+
readonly clause: "reflective material surfaces with controlled highlights";
|
|
98
|
+
readonly description: "Emphasizes reflections and highlight control on surfaces.";
|
|
99
|
+
readonly id: "reflective";
|
|
100
|
+
readonly label: "Reflective";
|
|
101
|
+
}];
|
|
102
|
+
readonly motion: readonly [{
|
|
103
|
+
readonly clause: "still composition with no motion blur";
|
|
104
|
+
readonly description: "Freezes the subject without implied movement.";
|
|
105
|
+
readonly id: "still";
|
|
106
|
+
readonly label: "Still";
|
|
107
|
+
}];
|
|
108
|
+
};
|
|
109
|
+
/** Result of applying creative direction to a base prompt. */
|
|
110
|
+
interface CreativePromptResult {
|
|
111
|
+
/** Sanitized user prompt before Motif adds creative clauses. */
|
|
112
|
+
basePrompt: string;
|
|
113
|
+
creative: {
|
|
114
|
+
/** Clauses appended to the prompt, de-duplicated in canonical field order. */
|
|
115
|
+
clauses: string[];
|
|
116
|
+
/** Validated option ids that were applied. */
|
|
117
|
+
selected: CreativeDirection;
|
|
118
|
+
};
|
|
119
|
+
/** Final prompt sent to fal after creative enrichment. */
|
|
120
|
+
prompt: string;
|
|
121
|
+
}
|
|
122
|
+
/** Input for Motif's prompt enrichment step. */
|
|
123
|
+
interface EnrichPromptOptions {
|
|
124
|
+
/** Optional selected creative option ids. */
|
|
125
|
+
creative?: CreativeDirection;
|
|
126
|
+
/** User-authored prompt before Motif normalization and enrichment. */
|
|
127
|
+
prompt: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Remove control characters and surrounding whitespace from a prompt.
|
|
131
|
+
*
|
|
132
|
+
* Newline style is normalized to `\n`; other text content is left unchanged.
|
|
133
|
+
*/
|
|
134
|
+
declare function sanitizePrompt(prompt: string): string;
|
|
135
|
+
/**
|
|
136
|
+
* Append selected creative direction clauses to a prompt.
|
|
137
|
+
*
|
|
138
|
+
* Options are validated against `CREATIVE_TAXONOMY`, applied in
|
|
139
|
+
* `CREATIVE_FIELDS` order, and returned as metadata alongside the final prompt.
|
|
140
|
+
*/
|
|
141
|
+
declare function enrichPrompt(options: EnrichPromptOptions): CreativePromptResult;
|
|
142
|
+
|
|
6
143
|
/** ─── Model Types ─────────────────────────────────────────────── */
|
|
7
144
|
type AspectRatio = "auto" | "8:1" | "4:1" | "21:9" | "16:9" | "3:2" | "4:3" | "5:4" | "1:1" | "4:5" | "3:4" | "2:3" | "9:16" | "1:4" | "1:8";
|
|
8
145
|
type Resolution = "0.5K" | "1K" | "2K" | "4K";
|
|
@@ -104,6 +241,13 @@ interface GenerateOptions {
|
|
|
104
241
|
aspect?: AspectRatio;
|
|
105
242
|
/** GPT background mode where supported */
|
|
106
243
|
background?: BackgroundMode;
|
|
244
|
+
/**
|
|
245
|
+
* Motif prompt enrichment choices applied before fal request construction.
|
|
246
|
+
*
|
|
247
|
+
* These options are not sent to fal directly; they append validated creative
|
|
248
|
+
* clauses to `prompt` and are omitted from the final request body.
|
|
249
|
+
*/
|
|
250
|
+
creative?: CreativeDirection;
|
|
107
251
|
editImageUrls?: string[];
|
|
108
252
|
/** Ask fal not to store IO payloads, and expose request ids for deletion. */
|
|
109
253
|
ephemeral?: boolean;
|
|
@@ -1137,4 +1281,4 @@ type FalToolId = (typeof FAL_TOOL_IDS)[number];
|
|
|
1137
1281
|
declare function isFalToolId(tool: string): tool is FalToolId;
|
|
1138
1282
|
declare function buildFalToolRequest(options: FalToolRunOptions): FalToolRequest;
|
|
1139
1283
|
|
|
1140
|
-
export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, type CustomImageSize, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv };
|
|
1284
|
+
export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, CREATIVE_FIELDS, CREATIVE_TAXONOMY, type CreativeDirection, type CreativeField, type CreativeOption, CreativeOptionError, type CreativeOptionErrorDetails, type CreativePromptResult, type CustomImageSize, type EnrichPromptOptions, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, enrichPrompt, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv, sanitizePrompt };
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,143 @@ export { Result, ResultAsync, err, ok } from 'neverthrow';
|
|
|
3
3
|
import * as _howells_envy from '@howells/envy';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Creative direction fields applied to prompts in Motif's canonical order.
|
|
8
|
+
*
|
|
9
|
+
* The order matters when multiple fields are selected because prompt clauses
|
|
10
|
+
* are appended in this sequence for stable dry runs, tests, and history.
|
|
11
|
+
*/
|
|
12
|
+
type CreativeField = "recipe" | "shot" | "lighting" | "genre" | "camera" | "color" | "material" | "motion";
|
|
13
|
+
/**
|
|
14
|
+
* Selected creative option ids keyed by direction field.
|
|
15
|
+
*
|
|
16
|
+
* Values must match option ids from `CREATIVE_TAXONOMY`; unknown ids throw a
|
|
17
|
+
* `CreativeOptionError` before any fal request body is built.
|
|
18
|
+
*/
|
|
19
|
+
type CreativeDirection = Partial<Record<CreativeField, string>>;
|
|
20
|
+
/** A single selectable creative direction option exposed to CLI and MCP schemas. */
|
|
21
|
+
interface CreativeOption {
|
|
22
|
+
/** Prompt fragment appended when this option is selected. */
|
|
23
|
+
clause: string;
|
|
24
|
+
/** Human-facing explanation used in schema metadata and generated docs. */
|
|
25
|
+
description: string;
|
|
26
|
+
/** Stable machine id accepted by `CreativeDirection`. */
|
|
27
|
+
id: string;
|
|
28
|
+
/** Short display label for UIs and schema enum descriptions. */
|
|
29
|
+
label: string;
|
|
30
|
+
}
|
|
31
|
+
/** Structured details returned when a creative direction id is not recognized. */
|
|
32
|
+
interface CreativeOptionErrorDetails {
|
|
33
|
+
availableIds: string[];
|
|
34
|
+
code: "INVALID_OPTION";
|
|
35
|
+
field: CreativeField;
|
|
36
|
+
value: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Error thrown when prompt enrichment receives an unknown creative option id.
|
|
40
|
+
*
|
|
41
|
+
* The extra fields make CLI and agent callers able to show field-specific
|
|
42
|
+
* recovery hints without parsing the error message.
|
|
43
|
+
*/
|
|
44
|
+
declare class CreativeOptionError extends Error implements CreativeOptionErrorDetails {
|
|
45
|
+
readonly availableIds: string[];
|
|
46
|
+
readonly code = "INVALID_OPTION";
|
|
47
|
+
readonly field: CreativeField;
|
|
48
|
+
readonly value: string;
|
|
49
|
+
constructor(details: Omit<CreativeOptionErrorDetails, "code">);
|
|
50
|
+
}
|
|
51
|
+
/** Canonical creative field order used for prompt enrichment and schema output. */
|
|
52
|
+
declare const CREATIVE_FIELDS: readonly ["recipe", "shot", "lighting", "genre", "camera", "color", "material", "motion"];
|
|
53
|
+
/**
|
|
54
|
+
* Built-in creative direction catalog.
|
|
55
|
+
*
|
|
56
|
+
* Each field contains the option ids accepted by `CreativeDirection` and the
|
|
57
|
+
* exact prompt clause that will be appended when selected.
|
|
58
|
+
*/
|
|
59
|
+
declare const CREATIVE_TAXONOMY: {
|
|
60
|
+
readonly recipe: readonly [{
|
|
61
|
+
readonly clause: "cinematic scene";
|
|
62
|
+
readonly description: "Frames the prompt as a cinematic still or scene.";
|
|
63
|
+
readonly id: "cinematic";
|
|
64
|
+
readonly label: "Cinematic";
|
|
65
|
+
}];
|
|
66
|
+
readonly shot: readonly [{
|
|
67
|
+
readonly clause: "close-up composition with controlled depth of field";
|
|
68
|
+
readonly description: "Tight framing that emphasizes subject detail.";
|
|
69
|
+
readonly id: "close-up";
|
|
70
|
+
readonly label: "Close-up";
|
|
71
|
+
}];
|
|
72
|
+
readonly lighting: readonly [{
|
|
73
|
+
readonly clause: "rim lighting with defined edge highlights";
|
|
74
|
+
readonly description: "Back or side light that separates the subject edge.";
|
|
75
|
+
readonly id: "rim";
|
|
76
|
+
readonly label: "Rim";
|
|
77
|
+
}];
|
|
78
|
+
readonly genre: readonly [{
|
|
79
|
+
readonly clause: "film noir mood with high contrast shadows";
|
|
80
|
+
readonly description: "High-contrast cinematic mood with shadow-forward drama.";
|
|
81
|
+
readonly id: "film-noir";
|
|
82
|
+
readonly label: "Film noir";
|
|
83
|
+
}];
|
|
84
|
+
readonly camera: readonly [{
|
|
85
|
+
readonly clause: "macro product photography with crisp surface detail";
|
|
86
|
+
readonly description: "Product-oriented macro camera language for close detail.";
|
|
87
|
+
readonly id: "macro-product";
|
|
88
|
+
readonly label: "Macro product";
|
|
89
|
+
}];
|
|
90
|
+
readonly color: readonly [{
|
|
91
|
+
readonly clause: "monochrome palette with tonal contrast";
|
|
92
|
+
readonly description: "Black-and-white or single-channel tonal treatment.";
|
|
93
|
+
readonly id: "monochrome";
|
|
94
|
+
readonly label: "Monochrome";
|
|
95
|
+
}];
|
|
96
|
+
readonly material: readonly [{
|
|
97
|
+
readonly clause: "reflective material surfaces with controlled highlights";
|
|
98
|
+
readonly description: "Emphasizes reflections and highlight control on surfaces.";
|
|
99
|
+
readonly id: "reflective";
|
|
100
|
+
readonly label: "Reflective";
|
|
101
|
+
}];
|
|
102
|
+
readonly motion: readonly [{
|
|
103
|
+
readonly clause: "still composition with no motion blur";
|
|
104
|
+
readonly description: "Freezes the subject without implied movement.";
|
|
105
|
+
readonly id: "still";
|
|
106
|
+
readonly label: "Still";
|
|
107
|
+
}];
|
|
108
|
+
};
|
|
109
|
+
/** Result of applying creative direction to a base prompt. */
|
|
110
|
+
interface CreativePromptResult {
|
|
111
|
+
/** Sanitized user prompt before Motif adds creative clauses. */
|
|
112
|
+
basePrompt: string;
|
|
113
|
+
creative: {
|
|
114
|
+
/** Clauses appended to the prompt, de-duplicated in canonical field order. */
|
|
115
|
+
clauses: string[];
|
|
116
|
+
/** Validated option ids that were applied. */
|
|
117
|
+
selected: CreativeDirection;
|
|
118
|
+
};
|
|
119
|
+
/** Final prompt sent to fal after creative enrichment. */
|
|
120
|
+
prompt: string;
|
|
121
|
+
}
|
|
122
|
+
/** Input for Motif's prompt enrichment step. */
|
|
123
|
+
interface EnrichPromptOptions {
|
|
124
|
+
/** Optional selected creative option ids. */
|
|
125
|
+
creative?: CreativeDirection;
|
|
126
|
+
/** User-authored prompt before Motif normalization and enrichment. */
|
|
127
|
+
prompt: string;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Remove control characters and surrounding whitespace from a prompt.
|
|
131
|
+
*
|
|
132
|
+
* Newline style is normalized to `\n`; other text content is left unchanged.
|
|
133
|
+
*/
|
|
134
|
+
declare function sanitizePrompt(prompt: string): string;
|
|
135
|
+
/**
|
|
136
|
+
* Append selected creative direction clauses to a prompt.
|
|
137
|
+
*
|
|
138
|
+
* Options are validated against `CREATIVE_TAXONOMY`, applied in
|
|
139
|
+
* `CREATIVE_FIELDS` order, and returned as metadata alongside the final prompt.
|
|
140
|
+
*/
|
|
141
|
+
declare function enrichPrompt(options: EnrichPromptOptions): CreativePromptResult;
|
|
142
|
+
|
|
6
143
|
/** ─── Model Types ─────────────────────────────────────────────── */
|
|
7
144
|
type AspectRatio = "auto" | "8:1" | "4:1" | "21:9" | "16:9" | "3:2" | "4:3" | "5:4" | "1:1" | "4:5" | "3:4" | "2:3" | "9:16" | "1:4" | "1:8";
|
|
8
145
|
type Resolution = "0.5K" | "1K" | "2K" | "4K";
|
|
@@ -104,6 +241,13 @@ interface GenerateOptions {
|
|
|
104
241
|
aspect?: AspectRatio;
|
|
105
242
|
/** GPT background mode where supported */
|
|
106
243
|
background?: BackgroundMode;
|
|
244
|
+
/**
|
|
245
|
+
* Motif prompt enrichment choices applied before fal request construction.
|
|
246
|
+
*
|
|
247
|
+
* These options are not sent to fal directly; they append validated creative
|
|
248
|
+
* clauses to `prompt` and are omitted from the final request body.
|
|
249
|
+
*/
|
|
250
|
+
creative?: CreativeDirection;
|
|
107
251
|
editImageUrls?: string[];
|
|
108
252
|
/** Ask fal not to store IO payloads, and expose request ids for deletion. */
|
|
109
253
|
ephemeral?: boolean;
|
|
@@ -1137,4 +1281,4 @@ type FalToolId = (typeof FAL_TOOL_IDS)[number];
|
|
|
1137
1281
|
declare function isFalToolId(tool: string): tool is FalToolId;
|
|
1138
1282
|
declare function buildFalToolRequest(options: FalToolRunOptions): FalToolRequest;
|
|
1139
1283
|
|
|
1140
|
-
export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, type CustomImageSize, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv };
|
|
1284
|
+
export { ASPECT_RATIOS, type AspectRatio, type BackgroundMode, CREATIVE_FIELDS, CREATIVE_TAXONOMY, type CreativeDirection, type CreativeField, type CreativeOption, CreativeOptionError, type CreativeOptionErrorDetails, type CreativePromptResult, type CustomImageSize, type EnrichPromptOptions, FAL_TOOLS, FAL_TOOLS_CHECKED_AT, FAL_TOOL_IDS, FORMAT_PRESETS, type FalImageSizePreset, type FalToolConfig, type FalToolId, type FalToolInputKind, type FalToolRequest, type FalToolRunOptions, GENERATION_MODELS, type GenerateOptions, type GenerationModelName, type GptImageSize, IDEOGRAM_STYLES, IMAGE_EDITING_TOP_20, IMAGE_TEXT_TO_IMAGE_TOP_20, type ImageOutputFormat, type ImageQuality, type ImageSize, type JobStatus, type LeaderboardEntry, type LeaderboardSnapshot, MODELS, type ModelConfig, type ModelType, type MotifEnv, MotifError, type MotifImage, type MotifResponse, MotifServer, type MotifServerConfig, type QueuedJob, RECRAFT_STYLES, RESOLUTIONS, type RemoveBackgroundOptions, type Resolution, type SizeMode, type ThinkingLevel, type ToolResponse, type ToolRunOptions, UTILITY_MODELS, type UpscaleOptions, VIDEO_IMAGE_TO_VIDEO_TOP_15, VIDEO_MODELS, VIDEO_TEXT_TO_VIDEO_TOP_15, type VideoOptions, type VideoResponse, aspectToFalImageSize, aspectToGptSize, buildFalToolRequest, buildGenerateBody, enrichPrompt, estimateCost, estimateVideoCost, getFalKeyFromEnv, isFalToolId, motifEnvSchema, parseMotifEnv, sanitizePrompt };
|
package/dist/index.js
CHANGED
|
@@ -883,6 +883,10 @@ function estimateCost(model, resolution, numImages = 1) {
|
|
|
883
883
|
if ((model === "banana" || model === "gemini3") && resolution === "4K") {
|
|
884
884
|
return configuredPrice * 2 * numImages;
|
|
885
885
|
}
|
|
886
|
+
if (model === "banana2") {
|
|
887
|
+
const multiplier = resolution === "4K" ? 2 : resolution === "2K" ? 1.5 : resolution === "0.5K" ? 0.75 : 1;
|
|
888
|
+
return configuredPrice * multiplier * numImages;
|
|
889
|
+
}
|
|
886
890
|
return configuredPrice * numImages;
|
|
887
891
|
}
|
|
888
892
|
switch (model) {
|
|
@@ -916,6 +920,136 @@ function estimateVideoCost(durationSeconds = 5, generateAudio = true) {
|
|
|
916
920
|
return perSecond * durationSeconds;
|
|
917
921
|
}
|
|
918
922
|
|
|
923
|
+
// src/creative.ts
|
|
924
|
+
var CONTROL_CHAR_REGEX = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g;
|
|
925
|
+
var CreativeOptionError = class extends Error {
|
|
926
|
+
availableIds;
|
|
927
|
+
code = "INVALID_OPTION";
|
|
928
|
+
field;
|
|
929
|
+
value;
|
|
930
|
+
constructor(details) {
|
|
931
|
+
super(
|
|
932
|
+
`Unknown creative ${details.field}: ${details.value}. Available: ${details.availableIds.join(", ")}`
|
|
933
|
+
);
|
|
934
|
+
this.name = "CreativeOptionError";
|
|
935
|
+
this.availableIds = details.availableIds;
|
|
936
|
+
this.field = details.field;
|
|
937
|
+
this.value = details.value;
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
var CREATIVE_FIELDS = [
|
|
941
|
+
"recipe",
|
|
942
|
+
"shot",
|
|
943
|
+
"lighting",
|
|
944
|
+
"genre",
|
|
945
|
+
"camera",
|
|
946
|
+
"color",
|
|
947
|
+
"material",
|
|
948
|
+
"motion"
|
|
949
|
+
];
|
|
950
|
+
var CREATIVE_TAXONOMY = {
|
|
951
|
+
recipe: [
|
|
952
|
+
{
|
|
953
|
+
clause: "cinematic scene",
|
|
954
|
+
description: "Frames the prompt as a cinematic still or scene.",
|
|
955
|
+
id: "cinematic",
|
|
956
|
+
label: "Cinematic"
|
|
957
|
+
}
|
|
958
|
+
],
|
|
959
|
+
shot: [
|
|
960
|
+
{
|
|
961
|
+
clause: "close-up composition with controlled depth of field",
|
|
962
|
+
description: "Tight framing that emphasizes subject detail.",
|
|
963
|
+
id: "close-up",
|
|
964
|
+
label: "Close-up"
|
|
965
|
+
}
|
|
966
|
+
],
|
|
967
|
+
lighting: [
|
|
968
|
+
{
|
|
969
|
+
clause: "rim lighting with defined edge highlights",
|
|
970
|
+
description: "Back or side light that separates the subject edge.",
|
|
971
|
+
id: "rim",
|
|
972
|
+
label: "Rim"
|
|
973
|
+
}
|
|
974
|
+
],
|
|
975
|
+
genre: [
|
|
976
|
+
{
|
|
977
|
+
clause: "film noir mood with high contrast shadows",
|
|
978
|
+
description: "High-contrast cinematic mood with shadow-forward drama.",
|
|
979
|
+
id: "film-noir",
|
|
980
|
+
label: "Film noir"
|
|
981
|
+
}
|
|
982
|
+
],
|
|
983
|
+
camera: [
|
|
984
|
+
{
|
|
985
|
+
clause: "macro product photography with crisp surface detail",
|
|
986
|
+
description: "Product-oriented macro camera language for close detail.",
|
|
987
|
+
id: "macro-product",
|
|
988
|
+
label: "Macro product"
|
|
989
|
+
}
|
|
990
|
+
],
|
|
991
|
+
color: [
|
|
992
|
+
{
|
|
993
|
+
clause: "monochrome palette with tonal contrast",
|
|
994
|
+
description: "Black-and-white or single-channel tonal treatment.",
|
|
995
|
+
id: "monochrome",
|
|
996
|
+
label: "Monochrome"
|
|
997
|
+
}
|
|
998
|
+
],
|
|
999
|
+
material: [
|
|
1000
|
+
{
|
|
1001
|
+
clause: "reflective material surfaces with controlled highlights",
|
|
1002
|
+
description: "Emphasizes reflections and highlight control on surfaces.",
|
|
1003
|
+
id: "reflective",
|
|
1004
|
+
label: "Reflective"
|
|
1005
|
+
}
|
|
1006
|
+
],
|
|
1007
|
+
motion: [
|
|
1008
|
+
{
|
|
1009
|
+
clause: "still composition with no motion blur",
|
|
1010
|
+
description: "Freezes the subject without implied movement.",
|
|
1011
|
+
id: "still",
|
|
1012
|
+
label: "Still"
|
|
1013
|
+
}
|
|
1014
|
+
]
|
|
1015
|
+
};
|
|
1016
|
+
function sanitizePrompt(prompt) {
|
|
1017
|
+
return prompt.replace(CONTROL_CHAR_REGEX, "").replace(/\r\n/g, "\n").trim();
|
|
1018
|
+
}
|
|
1019
|
+
function enrichPrompt(options) {
|
|
1020
|
+
const basePrompt = sanitizePrompt(options.prompt);
|
|
1021
|
+
const clauses = [];
|
|
1022
|
+
const selected = {};
|
|
1023
|
+
for (const field of CREATIVE_FIELDS) {
|
|
1024
|
+
const optionId = options.creative?.[field];
|
|
1025
|
+
if (!optionId) {
|
|
1026
|
+
continue;
|
|
1027
|
+
}
|
|
1028
|
+
const option = CREATIVE_TAXONOMY[field].find(
|
|
1029
|
+
(candidate) => candidate.id === optionId
|
|
1030
|
+
);
|
|
1031
|
+
if (!option) {
|
|
1032
|
+
throw new CreativeOptionError({
|
|
1033
|
+
availableIds: CREATIVE_TAXONOMY[field].map((candidate) => candidate.id),
|
|
1034
|
+
field,
|
|
1035
|
+
value: optionId
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
selected[field] = option.id;
|
|
1039
|
+
if (!clauses.includes(option.clause)) {
|
|
1040
|
+
clauses.push(option.clause);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
return {
|
|
1044
|
+
basePrompt,
|
|
1045
|
+
creative: {
|
|
1046
|
+
clauses,
|
|
1047
|
+
selected
|
|
1048
|
+
},
|
|
1049
|
+
prompt: clauses.length ? [basePrompt, ...clauses].join(", ") : basePrompt
|
|
1050
|
+
};
|
|
1051
|
+
}
|
|
1052
|
+
|
|
919
1053
|
// src/env.ts
|
|
920
1054
|
import { defineEnv } from "@howells/envy";
|
|
921
1055
|
import { z } from "zod";
|
|
@@ -1082,6 +1216,7 @@ function buildGenerateBody(options) {
|
|
|
1082
1216
|
const {
|
|
1083
1217
|
prompt,
|
|
1084
1218
|
model,
|
|
1219
|
+
creative,
|
|
1085
1220
|
aspect = "1:1",
|
|
1086
1221
|
resolution = "2K",
|
|
1087
1222
|
numImages = 1,
|
|
@@ -1116,7 +1251,8 @@ function buildGenerateBody(options) {
|
|
|
1116
1251
|
throw new Error(`Unknown model: ${model}`);
|
|
1117
1252
|
}
|
|
1118
1253
|
let endpoint = config.endpoint;
|
|
1119
|
-
const
|
|
1254
|
+
const enrichedPrompt = creative ? enrichPrompt({ prompt, creative }).prompt : prompt;
|
|
1255
|
+
const body = { prompt: enrichedPrompt };
|
|
1120
1256
|
const hasEditImages = Boolean(editImageUrls?.length);
|
|
1121
1257
|
const editImages = editImageUrls ?? [];
|
|
1122
1258
|
const explicitAspect = options.aspect !== void 0;
|
|
@@ -2245,7 +2381,13 @@ var MotifServer = class {
|
|
|
2245
2381
|
if (config?.useQueue) {
|
|
2246
2382
|
return this.generateQueued(options);
|
|
2247
2383
|
}
|
|
2248
|
-
|
|
2384
|
+
let built;
|
|
2385
|
+
try {
|
|
2386
|
+
built = buildGenerateBody(options);
|
|
2387
|
+
} catch (error) {
|
|
2388
|
+
return err(toMotifError(error));
|
|
2389
|
+
}
|
|
2390
|
+
const { endpoint, body } = built;
|
|
2249
2391
|
const response = await this.request(`${FAL_BASE_URL}/${endpoint}`, {
|
|
2250
2392
|
method: "POST",
|
|
2251
2393
|
body: JSON.stringify(body),
|
|
@@ -2287,7 +2429,13 @@ var MotifServer = class {
|
|
|
2287
2429
|
/** ─── Queue-Based Generation ──────────────────────────────── */
|
|
2288
2430
|
/** Submit a generation to the fal.ai queue (returns immediately). */
|
|
2289
2431
|
async submitGeneration(options) {
|
|
2290
|
-
|
|
2432
|
+
let built;
|
|
2433
|
+
try {
|
|
2434
|
+
built = buildGenerateBody(options);
|
|
2435
|
+
} catch (error) {
|
|
2436
|
+
return err(toMotifError(error));
|
|
2437
|
+
}
|
|
2438
|
+
const { endpoint, body } = built;
|
|
2291
2439
|
const response = await this.request(`${FAL_QUEUE_URL}/${endpoint}`, {
|
|
2292
2440
|
method: "POST",
|
|
2293
2441
|
body: JSON.stringify(body),
|
|
@@ -2687,8 +2835,19 @@ var MotifError = class extends Error {
|
|
|
2687
2835
|
this.code = code;
|
|
2688
2836
|
}
|
|
2689
2837
|
};
|
|
2838
|
+
function toMotifError(error) {
|
|
2839
|
+
if (error instanceof MotifError) {
|
|
2840
|
+
return error;
|
|
2841
|
+
}
|
|
2842
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2843
|
+
const code = error instanceof Error && "code" in error && typeof error.code === "string" ? error.code : void 0;
|
|
2844
|
+
return new MotifError(message, 0, code);
|
|
2845
|
+
}
|
|
2690
2846
|
export {
|
|
2691
2847
|
ASPECT_RATIOS,
|
|
2848
|
+
CREATIVE_FIELDS,
|
|
2849
|
+
CREATIVE_TAXONOMY,
|
|
2850
|
+
CreativeOptionError,
|
|
2692
2851
|
FAL_TOOLS,
|
|
2693
2852
|
FAL_TOOLS_CHECKED_AT,
|
|
2694
2853
|
FAL_TOOL_IDS,
|
|
@@ -2710,6 +2869,7 @@ export {
|
|
|
2710
2869
|
aspectToGptSize,
|
|
2711
2870
|
buildFalToolRequest,
|
|
2712
2871
|
buildGenerateBody,
|
|
2872
|
+
enrichPrompt,
|
|
2713
2873
|
err2 as err,
|
|
2714
2874
|
estimateCost,
|
|
2715
2875
|
estimateVideoCost,
|
|
@@ -2717,5 +2877,6 @@ export {
|
|
|
2717
2877
|
isFalToolId,
|
|
2718
2878
|
motifEnvSchema,
|
|
2719
2879
|
ok2 as ok,
|
|
2720
|
-
parseMotifEnv
|
|
2880
|
+
parseMotifEnv,
|
|
2881
|
+
sanitizePrompt
|
|
2721
2882
|
};
|