@umituz/react-native-ai-fal-provider 1.0.52 → 1.0.55
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/package.json +1 -1
- package/src/infrastructure/builders/image-feature-builder.ts +78 -0
- package/src/infrastructure/builders/index.ts +7 -0
- package/src/infrastructure/builders/video-feature-builder.ts +31 -0
- package/src/infrastructure/services/fal-provider.ts +10 -87
- package/src/infrastructure/utils/base-builders.util.ts +31 -0
- package/src/infrastructure/utils/image-feature-builders.util.ts +100 -0
- package/src/infrastructure/utils/input-builders.util.ts +3 -203
- package/src/infrastructure/utils/video-feature-builders.util.ts +43 -0
- package/src/infrastructure/validators/index.ts +6 -0
- package/src/infrastructure/validators/nsfw-validator.ts +23 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-fal-provider",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.55",
|
|
4
4
|
"description": "FAL AI provider for React Native - implements IAIProvider interface for unified AI generation",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Feature Input Builder
|
|
3
|
+
* Builds inputs for image-based AI features
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
ImageFeatureType,
|
|
8
|
+
ImageFeatureInputData,
|
|
9
|
+
} from "@umituz/react-native-ai-generation-content";
|
|
10
|
+
import { buildSingleImageInput } from "../utils/base-builders.util";
|
|
11
|
+
import {
|
|
12
|
+
buildUpscaleInput,
|
|
13
|
+
buildPhotoRestoreInput,
|
|
14
|
+
buildFaceSwapInput,
|
|
15
|
+
buildRemoveBackgroundInput,
|
|
16
|
+
buildReplaceBackgroundInput,
|
|
17
|
+
buildKontextStyleTransferInput,
|
|
18
|
+
} from "../utils/image-feature-builders.util";
|
|
19
|
+
|
|
20
|
+
export function buildImageFeatureInput(
|
|
21
|
+
feature: ImageFeatureType,
|
|
22
|
+
data: ImageFeatureInputData,
|
|
23
|
+
): Record<string, unknown> {
|
|
24
|
+
const { imageBase64, targetImageBase64, prompt, options } = data;
|
|
25
|
+
|
|
26
|
+
switch (feature) {
|
|
27
|
+
case "upscale":
|
|
28
|
+
case "hd-touch-up":
|
|
29
|
+
return buildUpscaleInput(imageBase64, options);
|
|
30
|
+
|
|
31
|
+
case "photo-restore":
|
|
32
|
+
return buildPhotoRestoreInput(imageBase64, options);
|
|
33
|
+
|
|
34
|
+
case "face-swap":
|
|
35
|
+
if (!targetImageBase64) {
|
|
36
|
+
throw new Error("Face swap requires target image");
|
|
37
|
+
}
|
|
38
|
+
return buildFaceSwapInput(imageBase64, targetImageBase64, options);
|
|
39
|
+
|
|
40
|
+
case "remove-background":
|
|
41
|
+
return buildRemoveBackgroundInput(imageBase64, options);
|
|
42
|
+
|
|
43
|
+
case "remove-object":
|
|
44
|
+
return buildRemoveObjectInput(imageBase64, prompt, options);
|
|
45
|
+
|
|
46
|
+
case "replace-background":
|
|
47
|
+
if (!prompt) {
|
|
48
|
+
throw new Error("Replace background requires prompt");
|
|
49
|
+
}
|
|
50
|
+
return buildReplaceBackgroundInput(imageBase64, { prompt, ...options });
|
|
51
|
+
|
|
52
|
+
case "anime-selfie":
|
|
53
|
+
return buildKontextStyleTransferInput(imageBase64, {
|
|
54
|
+
prompt: prompt || (options?.prompt as string) ||
|
|
55
|
+
"Transform this person into anime style illustration. Keep the same gender, face structure, hair color, eye color, and expression. Make it look like a high-quality anime character portrait with vibrant colors and clean lineart.",
|
|
56
|
+
guidance_scale: (options?.guidance_scale as number) ?? 4.0,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
default:
|
|
60
|
+
return buildSingleImageInput(imageBase64, options);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function buildRemoveObjectInput(
|
|
65
|
+
imageBase64: string,
|
|
66
|
+
prompt?: string,
|
|
67
|
+
options?: Record<string, unknown>,
|
|
68
|
+
): Record<string, unknown> {
|
|
69
|
+
return {
|
|
70
|
+
inpaint_image_url: imageBase64.startsWith("data:")
|
|
71
|
+
? imageBase64
|
|
72
|
+
: `data:image/jpeg;base64,${imageBase64}`,
|
|
73
|
+
prompt: prompt || (options?.prompt as string) ||
|
|
74
|
+
"Remove the object and fill with natural background",
|
|
75
|
+
inpaint_mode: "Modify Content (add objects, change background, etc.)",
|
|
76
|
+
guidance_scale: (options?.guidance_scale as number) ?? 4.0,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video Feature Input Builder
|
|
3
|
+
* Builds inputs for video-based AI features
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
VideoFeatureType,
|
|
8
|
+
VideoFeatureInputData,
|
|
9
|
+
} from "@umituz/react-native-ai-generation-content";
|
|
10
|
+
import { buildVideoFromImageInput } from "../utils/video-feature-builders.util";
|
|
11
|
+
|
|
12
|
+
const DEFAULT_VIDEO_PROMPTS: Record<VideoFeatureType, string> = {
|
|
13
|
+
"ai-kiss": "A romantic couple kissing tenderly, the two reference people sharing an intimate kiss moment, smooth natural movement, cinematic lighting, high quality video",
|
|
14
|
+
"ai-hug": "A heartwarming embrace between two people, the reference characters hugging warmly with genuine emotion, gentle natural movement, cinematic quality, touching moment",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export function buildVideoFeatureInput(
|
|
18
|
+
feature: VideoFeatureType,
|
|
19
|
+
data: VideoFeatureInputData,
|
|
20
|
+
): Record<string, unknown> {
|
|
21
|
+
const { sourceImageBase64, targetImageBase64, prompt, options } = data;
|
|
22
|
+
|
|
23
|
+
const effectivePrompt = prompt || DEFAULT_VIDEO_PROMPTS[feature] || "Generate video with natural motion";
|
|
24
|
+
|
|
25
|
+
return buildVideoFromImageInput(sourceImageBase64, {
|
|
26
|
+
prompt: effectivePrompt,
|
|
27
|
+
target_image: targetImageBase64,
|
|
28
|
+
aspect_ratio: (options?.aspect_ratio as "16:9" | "9:16" | "1:1") || "9:16",
|
|
29
|
+
movement_amplitude: (options?.movement_amplitude as "auto" | "small" | "medium" | "large") || "medium",
|
|
30
|
+
});
|
|
31
|
+
}
|
|
@@ -20,20 +20,11 @@ import type {
|
|
|
20
20
|
import type { FalQueueStatus } from "../../domain/entities/fal.types";
|
|
21
21
|
import { DEFAULT_FAL_CONFIG, FAL_CAPABILITIES } from "./fal-provider.constants";
|
|
22
22
|
import { mapFalStatusToJobStatus } from "./fal-status-mapper";
|
|
23
|
-
import { NSFWContentError } from "./nsfw-content-error";
|
|
24
23
|
import { FAL_IMAGE_FEATURE_MODELS, FAL_VIDEO_FEATURE_MODELS } from "../../domain/constants/feature-models.constants";
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
buildFaceSwapInput,
|
|
30
|
-
buildRemoveBackgroundInput,
|
|
31
|
-
buildReplaceBackgroundInput,
|
|
32
|
-
buildKontextStyleTransferInput,
|
|
33
|
-
buildVideoFromImageInput,
|
|
34
|
-
} from "../utils/input-builders.util";
|
|
35
|
-
|
|
36
|
-
declare const __DEV__: boolean;
|
|
24
|
+
import { buildImageFeatureInput as buildImageFeatureInputImpl, buildVideoFeatureInput as buildVideoFeatureInputImpl } from "../builders";
|
|
25
|
+
import { validateNSFWContent } from "../validators/nsfw-validator";
|
|
26
|
+
|
|
27
|
+
declare const __DEV__: boolean | undefined;
|
|
37
28
|
|
|
38
29
|
export class FalProvider implements IAIProvider {
|
|
39
30
|
readonly providerId = "fal";
|
|
@@ -159,7 +150,7 @@ export class FalProvider implements IAIProvider {
|
|
|
159
150
|
|
|
160
151
|
async run<T = unknown>(model: string, input: Record<string, unknown>, options?: RunOptions): Promise<T> {
|
|
161
152
|
this.validateInitialization();
|
|
162
|
-
options?.onProgress?.({ progress: 10, status: "IN_PROGRESS" });
|
|
153
|
+
options?.onProgress?.({ progress: 10, status: "IN_PROGRESS" as const });
|
|
163
154
|
|
|
164
155
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
165
156
|
console.log("[FalProvider] run() model:", model, "inputKeys:", Object.keys(input));
|
|
@@ -175,23 +166,12 @@ export class FalProvider implements IAIProvider {
|
|
|
175
166
|
|
|
176
167
|
this.checkForNSFWContent(result as Record<string, unknown>);
|
|
177
168
|
|
|
178
|
-
options?.onProgress?.({ progress: 100, status: "COMPLETED" });
|
|
169
|
+
options?.onProgress?.({ progress: 100, status: "COMPLETED" as const });
|
|
179
170
|
return result as T;
|
|
180
171
|
}
|
|
181
172
|
|
|
182
173
|
private checkForNSFWContent(result: Record<string, unknown>): void {
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
if (nsfwConcepts && Array.isArray(nsfwConcepts)) {
|
|
186
|
-
const hasNSFW = nsfwConcepts.some((value) => value === true);
|
|
187
|
-
|
|
188
|
-
if (hasNSFW) {
|
|
189
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
190
|
-
console.log("[FalProvider] NSFW content detected, rejecting result");
|
|
191
|
-
}
|
|
192
|
-
throw new NSFWContentError();
|
|
193
|
-
}
|
|
194
|
-
}
|
|
174
|
+
validateNSFWContent(result);
|
|
195
175
|
}
|
|
196
176
|
|
|
197
177
|
reset(): void {
|
|
@@ -205,49 +185,7 @@ export class FalProvider implements IAIProvider {
|
|
|
205
185
|
}
|
|
206
186
|
|
|
207
187
|
buildImageFeatureInput(feature: ImageFeatureType, data: ImageFeatureInputData): Record<string, unknown> {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
switch (feature) {
|
|
211
|
-
case "upscale":
|
|
212
|
-
case "hd-touch-up":
|
|
213
|
-
return buildUpscaleInput(imageBase64, options);
|
|
214
|
-
|
|
215
|
-
case "photo-restore":
|
|
216
|
-
return buildPhotoRestoreInput(imageBase64, options);
|
|
217
|
-
|
|
218
|
-
case "face-swap":
|
|
219
|
-
if (!targetImageBase64) throw new Error("Face swap requires target image");
|
|
220
|
-
return buildFaceSwapInput(imageBase64, targetImageBase64, options);
|
|
221
|
-
|
|
222
|
-
case "remove-background":
|
|
223
|
-
return buildRemoveBackgroundInput(imageBase64, options);
|
|
224
|
-
|
|
225
|
-
case "remove-object":
|
|
226
|
-
// Fooocus inpaint with "Modify Content" mode - no mask required
|
|
227
|
-
return {
|
|
228
|
-
inpaint_image_url: imageBase64.startsWith("data:")
|
|
229
|
-
? imageBase64
|
|
230
|
-
: `data:image/jpeg;base64,${imageBase64}`,
|
|
231
|
-
prompt: prompt || (options?.prompt as string) ||
|
|
232
|
-
"Remove the object and fill with natural background",
|
|
233
|
-
inpaint_mode: "Modify Content (add objects, change background, etc.)",
|
|
234
|
-
guidance_scale: (options?.guidance_scale as number) ?? 4.0,
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
case "replace-background":
|
|
238
|
-
if (!prompt) throw new Error("Replace background requires prompt");
|
|
239
|
-
return buildReplaceBackgroundInput(imageBase64, { prompt, ...options });
|
|
240
|
-
|
|
241
|
-
case "anime-selfie":
|
|
242
|
-
return buildKontextStyleTransferInput(imageBase64, {
|
|
243
|
-
prompt: prompt || (options?.prompt as string) ||
|
|
244
|
-
"Transform this person into anime style illustration. Keep the same gender, face structure, hair color, eye color, and expression. Make it look like a high-quality anime character portrait with vibrant colors and clean lineart.",
|
|
245
|
-
guidance_scale: (options?.guidance_scale as number) ?? 4.0,
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
default:
|
|
249
|
-
return buildSingleImageInput(imageBase64, options);
|
|
250
|
-
}
|
|
188
|
+
return buildImageFeatureInputImpl(feature, data);
|
|
251
189
|
}
|
|
252
190
|
|
|
253
191
|
getVideoFeatureModel(feature: VideoFeatureType): string {
|
|
@@ -255,23 +193,8 @@ export class FalProvider implements IAIProvider {
|
|
|
255
193
|
}
|
|
256
194
|
|
|
257
195
|
buildVideoFeatureInput(feature: VideoFeatureType, data: VideoFeatureInputData): Record<string, unknown> {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// Vidu Q1 optimized prompts for reference-to-video with multiple people
|
|
261
|
-
const defaultPrompts: Record<VideoFeatureType, string> = {
|
|
262
|
-
"ai-kiss": "A romantic couple kissing tenderly, the two reference people sharing an intimate kiss moment, smooth natural movement, cinematic lighting, high quality video",
|
|
263
|
-
"ai-hug": "A heartwarming embrace between two people, the reference characters hugging warmly with genuine emotion, gentle natural movement, cinematic quality, touching moment",
|
|
264
|
-
};
|
|
265
|
-
|
|
266
|
-
const effectivePrompt = prompt || defaultPrompts[feature] || "Generate video with natural motion";
|
|
267
|
-
|
|
268
|
-
return buildVideoFromImageInput(sourceImageBase64, {
|
|
269
|
-
prompt: effectivePrompt,
|
|
270
|
-
target_image: targetImageBase64,
|
|
271
|
-
aspect_ratio: (options?.aspect_ratio as "16:9" | "9:16" | "1:1") || "9:16",
|
|
272
|
-
movement_amplitude: (options?.movement_amplitude as "auto" | "small" | "medium" | "large") || "medium",
|
|
273
|
-
});
|
|
196
|
+
return buildVideoFeatureInputImpl(feature, data);
|
|
274
197
|
}
|
|
275
198
|
}
|
|
276
199
|
|
|
277
|
-
export const falProvider = new FalProvider();
|
|
200
|
+
export const falProvider = new FalProvider();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Input Builders
|
|
3
|
+
* Core builder functions for FAL API
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export function buildSingleImageInput(
|
|
7
|
+
base64: string,
|
|
8
|
+
extraParams?: Record<string, unknown>,
|
|
9
|
+
): Record<string, unknown> {
|
|
10
|
+
return {
|
|
11
|
+
image_url: base64.startsWith("data:")
|
|
12
|
+
? base64
|
|
13
|
+
: `data:image/jpeg;base64,${base64}`,
|
|
14
|
+
...extraParams,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function buildDualImageInput(
|
|
19
|
+
sourceBase64: string,
|
|
20
|
+
targetBase64: string,
|
|
21
|
+
extraParams?: Record<string, unknown>,
|
|
22
|
+
): Record<string, unknown> {
|
|
23
|
+
const formatImage = (b64: string) =>
|
|
24
|
+
b64.startsWith("data:") ? b64 : `data:image/jpeg;base64,${b64}`;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
image_url: formatImage(sourceBase64),
|
|
28
|
+
second_image_url: formatImage(targetBase64),
|
|
29
|
+
...extraParams,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Feature Input Builders
|
|
3
|
+
* Builder functions for specific image features
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
UpscaleOptions,
|
|
8
|
+
PhotoRestoreOptions,
|
|
9
|
+
RemoveBackgroundOptions,
|
|
10
|
+
RemoveObjectOptions,
|
|
11
|
+
ReplaceBackgroundOptions,
|
|
12
|
+
FaceSwapOptions,
|
|
13
|
+
} from "../../domain/types";
|
|
14
|
+
import { buildSingleImageInput } from "./base-builders.util";
|
|
15
|
+
|
|
16
|
+
export function buildUpscaleInput(
|
|
17
|
+
base64: string,
|
|
18
|
+
options?: UpscaleOptions,
|
|
19
|
+
): Record<string, unknown> {
|
|
20
|
+
return buildSingleImageInput(base64, {
|
|
21
|
+
scale: options?.scaleFactor || 2,
|
|
22
|
+
face_enhance: options?.enhanceFaces || false,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function buildPhotoRestoreInput(
|
|
27
|
+
base64: string,
|
|
28
|
+
options?: PhotoRestoreOptions,
|
|
29
|
+
): Record<string, unknown> {
|
|
30
|
+
return buildSingleImageInput(base64, {
|
|
31
|
+
face_enhance: options?.enhanceFaces || true,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function buildFaceSwapInput(
|
|
36
|
+
sourceBase64: string,
|
|
37
|
+
targetBase64: string,
|
|
38
|
+
_options?: FaceSwapOptions,
|
|
39
|
+
): Record<string, unknown> {
|
|
40
|
+
const formatImage = (b64: string) =>
|
|
41
|
+
b64.startsWith("data:") ? b64 : `data:image/jpeg;base64,${b64}`;
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
base_image_url: formatImage(sourceBase64),
|
|
45
|
+
swap_image_url: formatImage(targetBase64),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function buildRemoveBackgroundInput(
|
|
50
|
+
base64: string,
|
|
51
|
+
_options?: RemoveBackgroundOptions,
|
|
52
|
+
): Record<string, unknown> {
|
|
53
|
+
return buildSingleImageInput(base64, {
|
|
54
|
+
model: "General Use (Light)",
|
|
55
|
+
operating_resolution: "1024x1024",
|
|
56
|
+
output_format: "png",
|
|
57
|
+
refine_foreground: true,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function buildRemoveObjectInput(
|
|
62
|
+
base64: string,
|
|
63
|
+
options?: RemoveObjectOptions,
|
|
64
|
+
): Record<string, unknown> {
|
|
65
|
+
return buildSingleImageInput(base64, {
|
|
66
|
+
mask_url: options?.mask,
|
|
67
|
+
prompt: options?.prompt || "Remove the object and fill with background",
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function buildReplaceBackgroundInput(
|
|
72
|
+
base64: string,
|
|
73
|
+
options: ReplaceBackgroundOptions,
|
|
74
|
+
): Record<string, unknown> {
|
|
75
|
+
return buildSingleImageInput(base64, {
|
|
76
|
+
prompt: options.prompt,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function buildHDTouchUpInput(
|
|
81
|
+
base64: string,
|
|
82
|
+
options?: UpscaleOptions,
|
|
83
|
+
): Record<string, unknown> {
|
|
84
|
+
return buildUpscaleInput(base64, options);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface KontextStyleTransferOptions {
|
|
88
|
+
prompt: string;
|
|
89
|
+
guidance_scale?: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function buildKontextStyleTransferInput(
|
|
93
|
+
base64: string,
|
|
94
|
+
options: KontextStyleTransferOptions,
|
|
95
|
+
): Record<string, unknown> {
|
|
96
|
+
return buildSingleImageInput(base64, {
|
|
97
|
+
prompt: options.prompt,
|
|
98
|
+
guidance_scale: options.guidance_scale ?? 3.5,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
@@ -3,206 +3,6 @@
|
|
|
3
3
|
* Provider-agnostic: accepts prompt config as parameter, not imported
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
ImageToImagePromptConfig,
|
|
10
|
-
RemoveBackgroundOptions,
|
|
11
|
-
RemoveObjectOptions,
|
|
12
|
-
ReplaceBackgroundOptions,
|
|
13
|
-
VideoFromImageOptions,
|
|
14
|
-
FaceSwapOptions,
|
|
15
|
-
} from "../../domain/types";
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Build FAL single image input format
|
|
19
|
-
*/
|
|
20
|
-
export function buildSingleImageInput(
|
|
21
|
-
base64: string,
|
|
22
|
-
extraParams?: Record<string, unknown>,
|
|
23
|
-
): Record<string, unknown> {
|
|
24
|
-
return {
|
|
25
|
-
image_url: base64.startsWith("data:")
|
|
26
|
-
? base64
|
|
27
|
-
: `data:image/jpeg;base64,${base64}`,
|
|
28
|
-
...extraParams,
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Build FAL dual image input format
|
|
34
|
-
*/
|
|
35
|
-
export function buildDualImageInput(
|
|
36
|
-
sourceBase64: string,
|
|
37
|
-
targetBase64: string,
|
|
38
|
-
extraParams?: Record<string, unknown>,
|
|
39
|
-
): Record<string, unknown> {
|
|
40
|
-
const formatImage = (b64: string) =>
|
|
41
|
-
b64.startsWith("data:") ? b64 : `data:image/jpeg;base64,${b64}`;
|
|
42
|
-
|
|
43
|
-
return {
|
|
44
|
-
image_url: formatImage(sourceBase64),
|
|
45
|
-
second_image_url: formatImage(targetBase64),
|
|
46
|
-
...extraParams,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// =============================================================================
|
|
51
|
-
// FEATURE-SPECIFIC BUILDERS
|
|
52
|
-
// =============================================================================
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Build upscale input for FAL clarity-upscaler
|
|
56
|
-
*/
|
|
57
|
-
export function buildUpscaleInput(
|
|
58
|
-
base64: string,
|
|
59
|
-
options?: UpscaleOptions,
|
|
60
|
-
): Record<string, unknown> {
|
|
61
|
-
return buildSingleImageInput(base64, {
|
|
62
|
-
scale: options?.scaleFactor || 2,
|
|
63
|
-
face_enhance: options?.enhanceFaces || false,
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Build photo restore input for FAL aura-sr
|
|
69
|
-
*/
|
|
70
|
-
export function buildPhotoRestoreInput(
|
|
71
|
-
base64: string,
|
|
72
|
-
options?: PhotoRestoreOptions,
|
|
73
|
-
): Record<string, unknown> {
|
|
74
|
-
return buildSingleImageInput(base64, {
|
|
75
|
-
face_enhance: options?.enhanceFaces || true,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Build reference-to-video input for FAL Vidu Q1
|
|
81
|
-
* Supports up to 7 reference images for multi-person scenarios like kiss/hug
|
|
82
|
-
* Uses reference_image_urls array for consistent character appearance
|
|
83
|
-
*/
|
|
84
|
-
export function buildVideoFromImageInput(
|
|
85
|
-
base64: string,
|
|
86
|
-
options?: VideoFromImageOptions,
|
|
87
|
-
): Record<string, unknown> {
|
|
88
|
-
const formatImage = (b64: string) =>
|
|
89
|
-
b64.startsWith("data:") ? b64 : `data:image/jpeg;base64,${b64}`;
|
|
90
|
-
|
|
91
|
-
// Build reference images array - both source and target for kiss/hug
|
|
92
|
-
const referenceImages: string[] = [formatImage(base64)];
|
|
93
|
-
if (options?.target_image) {
|
|
94
|
-
referenceImages.push(formatImage(options.target_image));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
return {
|
|
98
|
-
prompt: options?.prompt || options?.motion_prompt || "Generate natural motion video",
|
|
99
|
-
reference_image_urls: referenceImages,
|
|
100
|
-
aspect_ratio: options?.aspect_ratio || "9:16",
|
|
101
|
-
movement_amplitude: options?.movement_amplitude || "auto",
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Build face swap input for FAL face-swap
|
|
107
|
-
* FAL API requires: base_image_url (target face) and swap_image_url (face to swap in)
|
|
108
|
-
*/
|
|
109
|
-
export function buildFaceSwapInput(
|
|
110
|
-
sourceBase64: string,
|
|
111
|
-
targetBase64: string,
|
|
112
|
-
_options?: FaceSwapOptions,
|
|
113
|
-
): Record<string, unknown> {
|
|
114
|
-
const formatImage = (b64: string) =>
|
|
115
|
-
b64.startsWith("data:") ? b64 : `data:image/jpeg;base64,${b64}`;
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
base_image_url: formatImage(sourceBase64),
|
|
119
|
-
swap_image_url: formatImage(targetBase64),
|
|
120
|
-
};
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/**
|
|
124
|
-
* Build image-to-image input for FAL flux/dev/image-to-image
|
|
125
|
-
* Accepts prompt config as parameter for provider-agnostic usage
|
|
126
|
-
*/
|
|
127
|
-
export function buildImageToImageInput(
|
|
128
|
-
base64: string,
|
|
129
|
-
promptConfig: ImageToImagePromptConfig,
|
|
130
|
-
): Record<string, unknown> {
|
|
131
|
-
return buildSingleImageInput(base64, {
|
|
132
|
-
prompt: promptConfig.prompt,
|
|
133
|
-
negative_prompt: promptConfig.negativePrompt,
|
|
134
|
-
strength: promptConfig.strength ?? 0.85,
|
|
135
|
-
num_inference_steps: promptConfig.num_inference_steps ?? 50,
|
|
136
|
-
guidance_scale: promptConfig.guidance_scale ?? 7.5,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Build remove background input for FAL birefnet
|
|
142
|
-
* Uses General Use (Light) model for fast processing
|
|
143
|
-
*/
|
|
144
|
-
export function buildRemoveBackgroundInput(
|
|
145
|
-
base64: string,
|
|
146
|
-
_options?: RemoveBackgroundOptions,
|
|
147
|
-
): Record<string, unknown> {
|
|
148
|
-
return buildSingleImageInput(base64, {
|
|
149
|
-
model: "General Use (Light)",
|
|
150
|
-
operating_resolution: "1024x1024",
|
|
151
|
-
output_format: "png",
|
|
152
|
-
refine_foreground: true,
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Build remove object (inpaint) input for FAL flux-kontext-lora/inpaint
|
|
158
|
-
*/
|
|
159
|
-
export function buildRemoveObjectInput(
|
|
160
|
-
base64: string,
|
|
161
|
-
options?: RemoveObjectOptions,
|
|
162
|
-
): Record<string, unknown> {
|
|
163
|
-
return buildSingleImageInput(base64, {
|
|
164
|
-
mask_url: options?.mask,
|
|
165
|
-
prompt: options?.prompt || "Remove the object and fill with background",
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Build replace background input for FAL bria/background/replace
|
|
171
|
-
*/
|
|
172
|
-
export function buildReplaceBackgroundInput(
|
|
173
|
-
base64: string,
|
|
174
|
-
options: ReplaceBackgroundOptions,
|
|
175
|
-
): Record<string, unknown> {
|
|
176
|
-
return buildSingleImageInput(base64, {
|
|
177
|
-
prompt: options.prompt,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Build HD touch up input (same as upscale)
|
|
183
|
-
*/
|
|
184
|
-
export function buildHDTouchUpInput(
|
|
185
|
-
base64: string,
|
|
186
|
-
options?: UpscaleOptions,
|
|
187
|
-
): Record<string, unknown> {
|
|
188
|
-
return buildUpscaleInput(base64, options);
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Build Kontext style transfer input for FAL flux-pro/kontext
|
|
193
|
-
* Instruction-based editing that preserves character identity
|
|
194
|
-
*/
|
|
195
|
-
export interface KontextStyleTransferOptions {
|
|
196
|
-
prompt: string;
|
|
197
|
-
guidance_scale?: number;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export function buildKontextStyleTransferInput(
|
|
201
|
-
base64: string,
|
|
202
|
-
options: KontextStyleTransferOptions,
|
|
203
|
-
): Record<string, unknown> {
|
|
204
|
-
return buildSingleImageInput(base64, {
|
|
205
|
-
prompt: options.prompt,
|
|
206
|
-
guidance_scale: options.guidance_scale ?? 3.5,
|
|
207
|
-
});
|
|
208
|
-
}
|
|
6
|
+
export * from "./base-builders.util";
|
|
7
|
+
export * from "./image-feature-builders.util";
|
|
8
|
+
export * from "./video-feature-builders.util";
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Video Feature Input Builders
|
|
3
|
+
* Builder functions for video features
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
ImageToImagePromptConfig,
|
|
8
|
+
VideoFromImageOptions,
|
|
9
|
+
} from "../../domain/types";
|
|
10
|
+
import { buildSingleImageInput } from "./base-builders.util";
|
|
11
|
+
|
|
12
|
+
export function buildImageToImageInput(
|
|
13
|
+
base64: string,
|
|
14
|
+
promptConfig: ImageToImagePromptConfig,
|
|
15
|
+
): Record<string, unknown> {
|
|
16
|
+
return buildSingleImageInput(base64, {
|
|
17
|
+
prompt: promptConfig.prompt,
|
|
18
|
+
negative_prompt: promptConfig.negativePrompt,
|
|
19
|
+
strength: promptConfig.strength ?? 0.85,
|
|
20
|
+
num_inference_steps: promptConfig.num_inference_steps ?? 50,
|
|
21
|
+
guidance_scale: promptConfig.guidance_scale ?? 7.5,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function buildVideoFromImageInput(
|
|
26
|
+
base64: string,
|
|
27
|
+
options?: VideoFromImageOptions,
|
|
28
|
+
): Record<string, unknown> {
|
|
29
|
+
const formatImage = (b64: string) =>
|
|
30
|
+
b64.startsWith("data:") ? b64 : `data:image/jpeg;base64,${b64}`;
|
|
31
|
+
|
|
32
|
+
const referenceImages: string[] = [formatImage(base64)];
|
|
33
|
+
if (options?.target_image) {
|
|
34
|
+
referenceImages.push(formatImage(options.target_image));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return {
|
|
38
|
+
prompt: options?.prompt || options?.motion_prompt || "Generate natural motion video",
|
|
39
|
+
reference_image_urls: referenceImages,
|
|
40
|
+
aspect_ratio: options?.aspect_ratio || "9:16",
|
|
41
|
+
movement_amplitude: options?.movement_amplitude || "auto",
|
|
42
|
+
};
|
|
43
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NSFW Content Validator
|
|
3
|
+
* Validates AI-generated content for NSFW material
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { NSFWContentError } from "../services/nsfw-content-error";
|
|
7
|
+
|
|
8
|
+
declare const __DEV__: boolean;
|
|
9
|
+
|
|
10
|
+
export function validateNSFWContent(result: Record<string, unknown>): void {
|
|
11
|
+
const nsfwConcepts = result?.has_nsfw_concepts as boolean[] | undefined;
|
|
12
|
+
|
|
13
|
+
if (nsfwConcepts && Array.isArray(nsfwConcepts)) {
|
|
14
|
+
const hasNSFW = nsfwConcepts.some((value) => value === true);
|
|
15
|
+
|
|
16
|
+
if (hasNSFW) {
|
|
17
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
18
|
+
console.log("[FalProvider] NSFW content detected, rejecting result");
|
|
19
|
+
}
|
|
20
|
+
throw new NSFWContentError();
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|