@umituz/react-native-ai-fal-provider 1.0.4 → 1.0.5
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 +4 -4
- package/src/domain/constants/default-models.constants.ts +181 -0
- package/src/domain/constants/feature-models.constants.ts +68 -0
- package/src/index.ts +59 -2
- package/src/infrastructure/services/fal-models.service.ts +198 -0
- package/src/infrastructure/services/fal-provider.ts +92 -0
- package/src/infrastructure/services/index.ts +1 -0
- package/src/infrastructure/utils/index.ts +26 -0
- package/src/infrastructure/utils/input-builders.util.ts +199 -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.5",
|
|
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",
|
|
@@ -33,13 +33,13 @@
|
|
|
33
33
|
"react-native": ">=0.74.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@fal-ai/client": "
|
|
36
|
+
"@fal-ai/client": ">=0.6.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
39
|
"@types/react": "~19.1.10",
|
|
40
40
|
"@typescript-eslint/eslint-plugin": "^7.0.0",
|
|
41
41
|
"@typescript-eslint/parser": "^7.0.0",
|
|
42
|
-
"@umituz/react-native-ai-generation-content": "^1.
|
|
42
|
+
"@umituz/react-native-ai-generation-content": "^1.17.26",
|
|
43
43
|
"eslint": "^8.57.0",
|
|
44
44
|
"react": "19.1.0",
|
|
45
45
|
"react-native": "0.81.5",
|
|
@@ -53,4 +53,4 @@
|
|
|
53
53
|
"README.md",
|
|
54
54
|
"LICENSE"
|
|
55
55
|
]
|
|
56
|
-
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default FAL AI Models Catalog
|
|
3
|
+
* Provides default model configurations for all FAL AI capabilities
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FalModelType } from "../entities/fal.types";
|
|
7
|
+
|
|
8
|
+
export interface FalModelConfig {
|
|
9
|
+
id: string;
|
|
10
|
+
name: string;
|
|
11
|
+
type: FalModelType;
|
|
12
|
+
/** @deprecated Use `type` instead. Kept for backward compatibility */
|
|
13
|
+
category?: string;
|
|
14
|
+
isDefault?: boolean;
|
|
15
|
+
isActive?: boolean;
|
|
16
|
+
pricing?: {
|
|
17
|
+
freeUserCost: number;
|
|
18
|
+
premiumUserCost: number;
|
|
19
|
+
};
|
|
20
|
+
description?: string;
|
|
21
|
+
order?: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const DEFAULT_TEXT_TO_IMAGE_MODELS: FalModelConfig[] = [
|
|
25
|
+
{
|
|
26
|
+
id: "fal-ai/flux/schnell",
|
|
27
|
+
name: "Flux Schnell",
|
|
28
|
+
type: "text-to-image",
|
|
29
|
+
isDefault: true,
|
|
30
|
+
isActive: true,
|
|
31
|
+
pricing: { freeUserCost: 1, premiumUserCost: 0.5 },
|
|
32
|
+
description: "Fast and efficient text-to-image generation",
|
|
33
|
+
order: 1,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "fal-ai/flux/dev",
|
|
37
|
+
name: "Flux Dev",
|
|
38
|
+
type: "text-to-image",
|
|
39
|
+
isDefault: false,
|
|
40
|
+
isActive: true,
|
|
41
|
+
pricing: { freeUserCost: 2, premiumUserCost: 1 },
|
|
42
|
+
description: "High-quality text-to-image generation",
|
|
43
|
+
order: 2,
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "fal-ai/flux-pro",
|
|
47
|
+
name: "Flux Pro",
|
|
48
|
+
type: "text-to-image",
|
|
49
|
+
isDefault: false,
|
|
50
|
+
isActive: true,
|
|
51
|
+
pricing: { freeUserCost: 3, premiumUserCost: 1.5 },
|
|
52
|
+
description: "Professional-grade text-to-image generation",
|
|
53
|
+
order: 3,
|
|
54
|
+
},
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
export const DEFAULT_TEXT_TO_VOICE_MODELS: FalModelConfig[] = [
|
|
58
|
+
{
|
|
59
|
+
id: "fal-ai/playai/tts/v3",
|
|
60
|
+
name: "PlayAI TTS v3",
|
|
61
|
+
type: "text-to-voice",
|
|
62
|
+
isDefault: true,
|
|
63
|
+
isActive: true,
|
|
64
|
+
pricing: { freeUserCost: 1, premiumUserCost: 0.5 },
|
|
65
|
+
description: "High-quality text-to-speech synthesis",
|
|
66
|
+
order: 1,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "fal-ai/eleven-labs/tts",
|
|
70
|
+
name: "ElevenLabs TTS",
|
|
71
|
+
type: "text-to-voice",
|
|
72
|
+
isDefault: false,
|
|
73
|
+
isActive: true,
|
|
74
|
+
pricing: { freeUserCost: 2, premiumUserCost: 1 },
|
|
75
|
+
description: "Premium voice synthesis with multiple voice options",
|
|
76
|
+
order: 2,
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
export const DEFAULT_TEXT_TO_VIDEO_MODELS: FalModelConfig[] = [
|
|
81
|
+
{
|
|
82
|
+
id: "fal-ai/hunyuan-video/1",
|
|
83
|
+
name: "Hunyuan",
|
|
84
|
+
type: "text-to-video",
|
|
85
|
+
isDefault: true,
|
|
86
|
+
isActive: true,
|
|
87
|
+
pricing: { freeUserCost: 10, premiumUserCost: 5 },
|
|
88
|
+
description: "High-quality video generation",
|
|
89
|
+
order: 1,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "fal-ai/minimax-video",
|
|
93
|
+
name: "MiniMax",
|
|
94
|
+
type: "text-to-video",
|
|
95
|
+
isDefault: false,
|
|
96
|
+
isActive: true,
|
|
97
|
+
pricing: { freeUserCost: 15, premiumUserCost: 8 },
|
|
98
|
+
description: "Advanced video generation with better dynamics",
|
|
99
|
+
order: 2,
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: "fal-ai/kling-video/v1.5/pro/text-to-video",
|
|
103
|
+
name: "Kling 1.5",
|
|
104
|
+
type: "text-to-video",
|
|
105
|
+
isDefault: false,
|
|
106
|
+
isActive: true,
|
|
107
|
+
pricing: { freeUserCost: 20, premiumUserCost: 10 },
|
|
108
|
+
description: "Professional video generation",
|
|
109
|
+
order: 3,
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "fal-ai/mochi-v1",
|
|
113
|
+
name: "Mochi",
|
|
114
|
+
type: "text-to-video",
|
|
115
|
+
isDefault: false,
|
|
116
|
+
isActive: true,
|
|
117
|
+
pricing: { freeUserCost: 8, premiumUserCost: 4 },
|
|
118
|
+
description: "Fast video generation",
|
|
119
|
+
order: 4,
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
|
|
123
|
+
export const DEFAULT_IMAGE_TO_VIDEO_MODELS: FalModelConfig[] = [
|
|
124
|
+
{
|
|
125
|
+
id: "fal-ai/kling-video/v1.5/pro/image-to-video",
|
|
126
|
+
name: "Kling I2V",
|
|
127
|
+
type: "image-to-video",
|
|
128
|
+
isDefault: true,
|
|
129
|
+
isActive: true,
|
|
130
|
+
pricing: { freeUserCost: 15, premiumUserCost: 8 },
|
|
131
|
+
description: "High-quality image to video generation",
|
|
132
|
+
order: 1,
|
|
133
|
+
},
|
|
134
|
+
];
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get all default models
|
|
138
|
+
*/
|
|
139
|
+
export function getAllDefaultModels(): FalModelConfig[] {
|
|
140
|
+
return [
|
|
141
|
+
...DEFAULT_TEXT_TO_IMAGE_MODELS,
|
|
142
|
+
...DEFAULT_TEXT_TO_VOICE_MODELS,
|
|
143
|
+
...DEFAULT_TEXT_TO_VIDEO_MODELS,
|
|
144
|
+
...DEFAULT_IMAGE_TO_VIDEO_MODELS,
|
|
145
|
+
];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Get default models by type
|
|
150
|
+
*/
|
|
151
|
+
export function getDefaultModelsByType(type: FalModelType): FalModelConfig[] {
|
|
152
|
+
switch (type) {
|
|
153
|
+
case "text-to-image":
|
|
154
|
+
return DEFAULT_TEXT_TO_IMAGE_MODELS;
|
|
155
|
+
case "text-to-voice":
|
|
156
|
+
return DEFAULT_TEXT_TO_VOICE_MODELS;
|
|
157
|
+
case "text-to-video":
|
|
158
|
+
return DEFAULT_TEXT_TO_VIDEO_MODELS;
|
|
159
|
+
case "image-to-video":
|
|
160
|
+
return DEFAULT_IMAGE_TO_VIDEO_MODELS;
|
|
161
|
+
case "image-to-image":
|
|
162
|
+
return [];
|
|
163
|
+
default:
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Get default model for a type
|
|
170
|
+
*/
|
|
171
|
+
export function getDefaultModel(type: FalModelType): FalModelConfig | undefined {
|
|
172
|
+
const models = getDefaultModelsByType(type);
|
|
173
|
+
return models.find((m) => m.isDefault) || models[0];
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Find model by ID across all types
|
|
178
|
+
*/
|
|
179
|
+
export function findModelById(id: string): FalModelConfig | undefined {
|
|
180
|
+
return getAllDefaultModels().find((m) => m.id === id);
|
|
181
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FAL Feature Models Catalog
|
|
3
|
+
* Provider-specific model IDs for image and video processing features
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
ImageFeatureType,
|
|
8
|
+
VideoFeatureType,
|
|
9
|
+
} from "@umituz/react-native-ai-generation-content";
|
|
10
|
+
|
|
11
|
+
export interface FeatureModelConfig {
|
|
12
|
+
id: string;
|
|
13
|
+
feature: ImageFeatureType | VideoFeatureType;
|
|
14
|
+
description?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* FAL model IDs for IMAGE processing features
|
|
19
|
+
*/
|
|
20
|
+
export const FAL_IMAGE_FEATURE_MODELS: Record<ImageFeatureType, string> = {
|
|
21
|
+
"upscale": "fal-ai/clarity-upscaler",
|
|
22
|
+
"photo-restore": "fal-ai/aura-sr",
|
|
23
|
+
"face-swap": "fal-ai/face-swap",
|
|
24
|
+
"anime-selfie": "fal-ai/anime-image-generator",
|
|
25
|
+
"remove-background": "fal-ai/bria/background/remove",
|
|
26
|
+
"remove-object": "fal-ai/flux-kontext-lora/inpaint",
|
|
27
|
+
"hd-touch-up": "fal-ai/clarity-upscaler",
|
|
28
|
+
"replace-background": "fal-ai/bria/background/replace",
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* FAL model IDs for VIDEO processing features
|
|
33
|
+
*/
|
|
34
|
+
export const FAL_VIDEO_FEATURE_MODELS: Record<VideoFeatureType, string> = {
|
|
35
|
+
"ai-hug": "fal-ai/wan-25-preview/image-to-video",
|
|
36
|
+
"ai-kiss": "fal-ai/wan-25-preview/image-to-video",
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Get FAL model ID for an image feature
|
|
41
|
+
*/
|
|
42
|
+
export function getFalImageFeatureModel(feature: ImageFeatureType): string {
|
|
43
|
+
return FAL_IMAGE_FEATURE_MODELS[feature];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get FAL model ID for a video feature
|
|
48
|
+
*/
|
|
49
|
+
export function getFalVideoFeatureModel(feature: VideoFeatureType): string {
|
|
50
|
+
return FAL_VIDEO_FEATURE_MODELS[feature];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Get all feature model configs
|
|
55
|
+
*/
|
|
56
|
+
export function getAllFeatureModels(): FeatureModelConfig[] {
|
|
57
|
+
const imageModels = Object.entries(FAL_IMAGE_FEATURE_MODELS).map(([feature, id]) => ({
|
|
58
|
+
id,
|
|
59
|
+
feature: feature as ImageFeatureType,
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
const videoModels = Object.entries(FAL_VIDEO_FEATURE_MODELS).map(([feature, id]) => ({
|
|
63
|
+
id,
|
|
64
|
+
feature: feature as VideoFeatureType,
|
|
65
|
+
}));
|
|
66
|
+
|
|
67
|
+
return [...imageModels, ...videoModels];
|
|
68
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -42,6 +42,39 @@ export type {
|
|
|
42
42
|
FalErrorMessages,
|
|
43
43
|
} from "./domain/entities/error.types";
|
|
44
44
|
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// DOMAIN LAYER - Default Models
|
|
47
|
+
// =============================================================================
|
|
48
|
+
|
|
49
|
+
export {
|
|
50
|
+
DEFAULT_TEXT_TO_IMAGE_MODELS,
|
|
51
|
+
DEFAULT_TEXT_TO_VOICE_MODELS,
|
|
52
|
+
DEFAULT_TEXT_TO_VIDEO_MODELS,
|
|
53
|
+
DEFAULT_IMAGE_TO_VIDEO_MODELS,
|
|
54
|
+
getAllDefaultModels,
|
|
55
|
+
getDefaultModelsByType,
|
|
56
|
+
getDefaultModel,
|
|
57
|
+
findModelById,
|
|
58
|
+
} from "./domain/constants/default-models.constants";
|
|
59
|
+
|
|
60
|
+
export type { FalModelConfig } from "./domain/constants/default-models.constants";
|
|
61
|
+
|
|
62
|
+
// =============================================================================
|
|
63
|
+
// DOMAIN LAYER - Feature Models
|
|
64
|
+
// =============================================================================
|
|
65
|
+
|
|
66
|
+
export {
|
|
67
|
+
FAL_IMAGE_FEATURE_MODELS,
|
|
68
|
+
FAL_VIDEO_FEATURE_MODELS,
|
|
69
|
+
getFalImageFeatureModel,
|
|
70
|
+
getFalVideoFeatureModel,
|
|
71
|
+
getAllFeatureModels,
|
|
72
|
+
} from "./domain/constants/feature-models.constants";
|
|
73
|
+
|
|
74
|
+
export type {
|
|
75
|
+
FeatureModelConfig,
|
|
76
|
+
} from "./domain/constants/feature-models.constants";
|
|
77
|
+
|
|
45
78
|
// =============================================================================
|
|
46
79
|
// INFRASTRUCTURE LAYER - Provider (IAIProvider Implementation)
|
|
47
80
|
// =============================================================================
|
|
@@ -49,10 +82,11 @@ export type {
|
|
|
49
82
|
export { FalProvider, falProvider } from "./infrastructure/services";
|
|
50
83
|
|
|
51
84
|
// =============================================================================
|
|
52
|
-
// INFRASTRUCTURE LAYER - Services
|
|
85
|
+
// INFRASTRUCTURE LAYER - Services
|
|
53
86
|
// =============================================================================
|
|
54
87
|
|
|
55
|
-
export { falClientService } from "./infrastructure/services";
|
|
88
|
+
export { falClientService, falModelsService } from "./infrastructure/services";
|
|
89
|
+
export type { ModelFetcher } from "./infrastructure/services";
|
|
56
90
|
|
|
57
91
|
// =============================================================================
|
|
58
92
|
// INFRASTRUCTURE LAYER - Utils
|
|
@@ -63,6 +97,29 @@ export {
|
|
|
63
97
|
falErrorMapper,
|
|
64
98
|
mapFalError,
|
|
65
99
|
isFalErrorRetryable,
|
|
100
|
+
// Input builders
|
|
101
|
+
buildSingleImageInput,
|
|
102
|
+
buildDualImageInput,
|
|
103
|
+
buildUpscaleInput,
|
|
104
|
+
buildPhotoRestoreInput,
|
|
105
|
+
buildVideoFromImageInput,
|
|
106
|
+
buildFaceSwapInput,
|
|
107
|
+
buildAnimeSelfieInput,
|
|
108
|
+
buildRemoveBackgroundInput,
|
|
109
|
+
buildRemoveObjectInput,
|
|
110
|
+
buildReplaceBackgroundInput,
|
|
111
|
+
buildHDTouchUpInput,
|
|
112
|
+
} from "./infrastructure/utils";
|
|
113
|
+
|
|
114
|
+
export type {
|
|
115
|
+
UpscaleOptions,
|
|
116
|
+
PhotoRestoreOptions,
|
|
117
|
+
FaceSwapOptions,
|
|
118
|
+
AnimeSelfieOptions,
|
|
119
|
+
RemoveBackgroundOptions,
|
|
120
|
+
RemoveObjectOptions,
|
|
121
|
+
ReplaceBackgroundOptions,
|
|
122
|
+
VideoFromImageOptions,
|
|
66
123
|
} from "./infrastructure/utils";
|
|
67
124
|
|
|
68
125
|
// =============================================================================
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FAL Models Service
|
|
3
|
+
* Manages FAL AI model configurations with in-memory caching
|
|
4
|
+
*
|
|
5
|
+
* This service provides default models and allows apps to override/extend
|
|
6
|
+
* with custom model sources (e.g., Firebase, API)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { FalModelType } from "../../domain/entities/fal.types";
|
|
10
|
+
import {
|
|
11
|
+
type FalModelConfig,
|
|
12
|
+
getDefaultModelsByType,
|
|
13
|
+
getDefaultModel,
|
|
14
|
+
findModelById,
|
|
15
|
+
} from "../../domain/constants/default-models.constants";
|
|
16
|
+
|
|
17
|
+
export type { FalModelConfig };
|
|
18
|
+
|
|
19
|
+
export type ModelFetcher = (type: FalModelType) => Promise<FalModelConfig[]>;
|
|
20
|
+
|
|
21
|
+
interface CacheEntry {
|
|
22
|
+
data: FalModelConfig[];
|
|
23
|
+
timestamp: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const DEFAULT_CACHE_TTL = 24 * 60 * 60 * 1000; // 24 hours
|
|
27
|
+
|
|
28
|
+
class FalModelsService {
|
|
29
|
+
private cache = new Map<FalModelType, CacheEntry>();
|
|
30
|
+
private cacheTtl = DEFAULT_CACHE_TTL;
|
|
31
|
+
private customFetcher: ModelFetcher | null = null;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Set custom model fetcher (e.g., for Firebase integration)
|
|
35
|
+
*/
|
|
36
|
+
setModelFetcher(fetcher: ModelFetcher): void {
|
|
37
|
+
this.customFetcher = fetcher;
|
|
38
|
+
this.clearCache();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Configure cache TTL
|
|
43
|
+
*/
|
|
44
|
+
setCacheTtl(ttlMs: number): void {
|
|
45
|
+
this.cacheTtl = ttlMs;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get models by type with caching
|
|
50
|
+
*/
|
|
51
|
+
async getModels(type: FalModelType): Promise<FalModelConfig[]> {
|
|
52
|
+
const cached = this.getFromCache(type);
|
|
53
|
+
if (cached) {
|
|
54
|
+
return cached;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let models: FalModelConfig[];
|
|
58
|
+
|
|
59
|
+
if (this.customFetcher) {
|
|
60
|
+
try {
|
|
61
|
+
models = await this.customFetcher(type);
|
|
62
|
+
if (models.length === 0) {
|
|
63
|
+
models = getDefaultModelsByType(type);
|
|
64
|
+
}
|
|
65
|
+
} catch {
|
|
66
|
+
models = getDefaultModelsByType(type);
|
|
67
|
+
}
|
|
68
|
+
} else {
|
|
69
|
+
models = getDefaultModelsByType(type);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
this.setCache(type, models);
|
|
73
|
+
return this.sortModels(models);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Get text-to-image models
|
|
78
|
+
*/
|
|
79
|
+
async getTextToImageModels(): Promise<FalModelConfig[]> {
|
|
80
|
+
return this.getModels("text-to-image");
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get text-to-voice models
|
|
85
|
+
*/
|
|
86
|
+
async getTextToVoiceModels(): Promise<FalModelConfig[]> {
|
|
87
|
+
return this.getModels("text-to-voice");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get text-to-video models
|
|
92
|
+
*/
|
|
93
|
+
async getTextToVideoModels(): Promise<FalModelConfig[]> {
|
|
94
|
+
return this.getModels("text-to-video");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Get image-to-video models
|
|
99
|
+
*/
|
|
100
|
+
async getImageToVideoModels(): Promise<FalModelConfig[]> {
|
|
101
|
+
return this.getModels("image-to-video");
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get default model for type
|
|
106
|
+
*/
|
|
107
|
+
getDefaultModel(type: FalModelType): FalModelConfig | undefined {
|
|
108
|
+
return getDefaultModel(type);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Find model by ID (from cache or defaults)
|
|
113
|
+
*/
|
|
114
|
+
async findById(id: string): Promise<FalModelConfig | undefined> {
|
|
115
|
+
// Check cache first
|
|
116
|
+
for (const [, entry] of this.cache) {
|
|
117
|
+
const found = entry.data.find((m) => m.id === id);
|
|
118
|
+
if (found) return found;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Fall back to defaults
|
|
122
|
+
return findModelById(id);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Get model pricing
|
|
127
|
+
*/
|
|
128
|
+
async getModelPricing(
|
|
129
|
+
modelId: string
|
|
130
|
+
): Promise<{ freeUserCost: number; premiumUserCost: number } | null> {
|
|
131
|
+
const model = await this.findById(modelId);
|
|
132
|
+
return model?.pricing || null;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Get voice model pricing
|
|
137
|
+
*/
|
|
138
|
+
async getVoiceModelPricing(
|
|
139
|
+
modelId: string
|
|
140
|
+
): Promise<{ freeUserCost: number; premiumUserCost: number } | null> {
|
|
141
|
+
const models = await this.getTextToVoiceModels();
|
|
142
|
+
const model = models.find((m) => m.id === modelId);
|
|
143
|
+
return model?.pricing || null;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get video model pricing
|
|
148
|
+
*/
|
|
149
|
+
async getVideoModelPricing(
|
|
150
|
+
modelId: string
|
|
151
|
+
): Promise<{ freeUserCost: number; premiumUserCost: number } | null> {
|
|
152
|
+
const models = await this.getTextToVideoModels();
|
|
153
|
+
const model = models.find((m) => m.id === modelId);
|
|
154
|
+
return model?.pricing || null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Clear all cached models
|
|
159
|
+
*/
|
|
160
|
+
clearCache(): void {
|
|
161
|
+
this.cache.clear();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Clear cache for specific type
|
|
166
|
+
*/
|
|
167
|
+
clearCacheForType(type: FalModelType): void {
|
|
168
|
+
this.cache.delete(type);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private getFromCache(type: FalModelType): FalModelConfig[] | null {
|
|
172
|
+
const entry = this.cache.get(type);
|
|
173
|
+
if (!entry) return null;
|
|
174
|
+
|
|
175
|
+
const isExpired = Date.now() - entry.timestamp > this.cacheTtl;
|
|
176
|
+
if (isExpired) {
|
|
177
|
+
this.cache.delete(type);
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return entry.data;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
private setCache(type: FalModelType, data: FalModelConfig[]): void {
|
|
185
|
+
this.cache.set(type, { data, timestamp: Date.now() });
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private sortModels(models: FalModelConfig[]): FalModelConfig[] {
|
|
189
|
+
return [...models].sort((a, b) => {
|
|
190
|
+
if (a.order !== b.order) {
|
|
191
|
+
return (a.order || 0) - (b.order || 0);
|
|
192
|
+
}
|
|
193
|
+
return a.name.localeCompare(b.name);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export const falModelsService = new FalModelsService();
|
|
@@ -11,8 +11,27 @@ import type {
|
|
|
11
11
|
JobStatus,
|
|
12
12
|
AIJobStatusType,
|
|
13
13
|
SubscribeOptions,
|
|
14
|
+
ImageFeatureType,
|
|
15
|
+
VideoFeatureType,
|
|
16
|
+
ImageFeatureInputData,
|
|
17
|
+
VideoFeatureInputData,
|
|
14
18
|
} from "@umituz/react-native-ai-generation-content";
|
|
15
19
|
import type { FalQueueStatus, FalLogEntry } from "../../domain/entities/fal.types";
|
|
20
|
+
import {
|
|
21
|
+
FAL_IMAGE_FEATURE_MODELS,
|
|
22
|
+
FAL_VIDEO_FEATURE_MODELS,
|
|
23
|
+
} from "../../domain/constants/feature-models.constants";
|
|
24
|
+
import {
|
|
25
|
+
buildUpscaleInput,
|
|
26
|
+
buildPhotoRestoreInput,
|
|
27
|
+
buildVideoFromImageInput,
|
|
28
|
+
buildFaceSwapInput,
|
|
29
|
+
buildAnimeSelfieInput,
|
|
30
|
+
buildRemoveBackgroundInput,
|
|
31
|
+
buildRemoveObjectInput,
|
|
32
|
+
buildReplaceBackgroundInput,
|
|
33
|
+
buildHDTouchUpInput,
|
|
34
|
+
} from "../utils/input-builders.util";
|
|
16
35
|
|
|
17
36
|
declare const __DEV__: boolean;
|
|
18
37
|
|
|
@@ -172,6 +191,79 @@ export class FalProvider implements IAIProvider {
|
|
|
172
191
|
this.config = null;
|
|
173
192
|
this.initialized = false;
|
|
174
193
|
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get model ID for an IMAGE feature
|
|
197
|
+
*/
|
|
198
|
+
getImageFeatureModel(feature: ImageFeatureType): string {
|
|
199
|
+
return FAL_IMAGE_FEATURE_MODELS[feature];
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Build input for an IMAGE feature
|
|
204
|
+
*/
|
|
205
|
+
buildImageFeatureInput(
|
|
206
|
+
feature: ImageFeatureType,
|
|
207
|
+
data: ImageFeatureInputData,
|
|
208
|
+
): Record<string, unknown> {
|
|
209
|
+
const { imageBase64, targetImageBase64, prompt, options } = data;
|
|
210
|
+
|
|
211
|
+
switch (feature) {
|
|
212
|
+
case "upscale":
|
|
213
|
+
return buildUpscaleInput(imageBase64, options);
|
|
214
|
+
case "photo-restore":
|
|
215
|
+
return buildPhotoRestoreInput(imageBase64, options);
|
|
216
|
+
case "face-swap":
|
|
217
|
+
if (!targetImageBase64) {
|
|
218
|
+
throw new Error("Face swap requires target image");
|
|
219
|
+
}
|
|
220
|
+
return buildFaceSwapInput(imageBase64, targetImageBase64, options);
|
|
221
|
+
case "anime-selfie":
|
|
222
|
+
return buildAnimeSelfieInput(imageBase64, options);
|
|
223
|
+
case "remove-background":
|
|
224
|
+
return buildRemoveBackgroundInput(imageBase64, options);
|
|
225
|
+
case "remove-object":
|
|
226
|
+
return buildRemoveObjectInput(imageBase64, { prompt, ...options });
|
|
227
|
+
case "hd-touch-up":
|
|
228
|
+
return buildHDTouchUpInput(imageBase64, options);
|
|
229
|
+
case "replace-background":
|
|
230
|
+
if (!prompt) {
|
|
231
|
+
throw new Error("Replace background requires prompt");
|
|
232
|
+
}
|
|
233
|
+
return buildReplaceBackgroundInput(imageBase64, { prompt });
|
|
234
|
+
default:
|
|
235
|
+
throw new Error(`Unknown image feature: ${feature}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get model ID for a VIDEO feature
|
|
241
|
+
*/
|
|
242
|
+
getVideoFeatureModel(feature: VideoFeatureType): string {
|
|
243
|
+
return FAL_VIDEO_FEATURE_MODELS[feature];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Build input for a VIDEO feature
|
|
248
|
+
*/
|
|
249
|
+
buildVideoFeatureInput(
|
|
250
|
+
feature: VideoFeatureType,
|
|
251
|
+
data: VideoFeatureInputData,
|
|
252
|
+
): Record<string, unknown> {
|
|
253
|
+
const { sourceImageBase64, targetImageBase64, prompt, options } = data;
|
|
254
|
+
|
|
255
|
+
switch (feature) {
|
|
256
|
+
case "ai-hug":
|
|
257
|
+
case "ai-kiss":
|
|
258
|
+
return buildVideoFromImageInput(sourceImageBase64, {
|
|
259
|
+
target_image: targetImageBase64,
|
|
260
|
+
motion_prompt: prompt,
|
|
261
|
+
...options,
|
|
262
|
+
});
|
|
263
|
+
default:
|
|
264
|
+
throw new Error(`Unknown video feature: ${feature}`);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
175
267
|
}
|
|
176
268
|
|
|
177
269
|
export const falProvider = new FalProvider();
|
|
@@ -5,3 +5,29 @@
|
|
|
5
5
|
|
|
6
6
|
export { categorizeFalError } from "./error-categorizer";
|
|
7
7
|
export { falErrorMapper, mapFalError, isFalErrorRetryable } from "./error-mapper";
|
|
8
|
+
|
|
9
|
+
// Input builders
|
|
10
|
+
export {
|
|
11
|
+
buildSingleImageInput,
|
|
12
|
+
buildDualImageInput,
|
|
13
|
+
buildUpscaleInput,
|
|
14
|
+
buildPhotoRestoreInput,
|
|
15
|
+
buildVideoFromImageInput,
|
|
16
|
+
buildFaceSwapInput,
|
|
17
|
+
buildAnimeSelfieInput,
|
|
18
|
+
buildRemoveBackgroundInput,
|
|
19
|
+
buildRemoveObjectInput,
|
|
20
|
+
buildReplaceBackgroundInput,
|
|
21
|
+
buildHDTouchUpInput,
|
|
22
|
+
} from "./input-builders.util";
|
|
23
|
+
|
|
24
|
+
export type {
|
|
25
|
+
UpscaleOptions,
|
|
26
|
+
PhotoRestoreOptions,
|
|
27
|
+
FaceSwapOptions,
|
|
28
|
+
AnimeSelfieOptions,
|
|
29
|
+
RemoveBackgroundOptions,
|
|
30
|
+
RemoveObjectOptions,
|
|
31
|
+
ReplaceBackgroundOptions,
|
|
32
|
+
VideoFromImageOptions,
|
|
33
|
+
} from "./input-builders.util";
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FAL Input Builders
|
|
3
|
+
* Constructs FAL API input from normalized data
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// TYPES
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
export interface UpscaleOptions {
|
|
11
|
+
scaleFactor?: number;
|
|
12
|
+
enhanceFaces?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface PhotoRestoreOptions {
|
|
16
|
+
enhanceFaces?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FaceSwapOptions {
|
|
20
|
+
// No additional options
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface AnimeSelfieOptions {
|
|
24
|
+
style?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface RemoveBackgroundOptions {
|
|
28
|
+
// No additional options
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface RemoveObjectOptions {
|
|
32
|
+
mask?: string;
|
|
33
|
+
prompt?: string;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface ReplaceBackgroundOptions {
|
|
37
|
+
prompt: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface VideoFromImageOptions {
|
|
41
|
+
target_image?: string;
|
|
42
|
+
motion_prompt?: string;
|
|
43
|
+
duration?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// BASE BUILDERS
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build FAL single image input format
|
|
52
|
+
*/
|
|
53
|
+
export function buildSingleImageInput(
|
|
54
|
+
base64: string,
|
|
55
|
+
extraParams?: Record<string, unknown>,
|
|
56
|
+
): Record<string, unknown> {
|
|
57
|
+
return {
|
|
58
|
+
image_url: base64.startsWith("data:")
|
|
59
|
+
? base64
|
|
60
|
+
: `data:image/jpeg;base64,${base64}`,
|
|
61
|
+
...extraParams,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Build FAL dual image input format
|
|
67
|
+
*/
|
|
68
|
+
export function buildDualImageInput(
|
|
69
|
+
sourceBase64: string,
|
|
70
|
+
targetBase64: string,
|
|
71
|
+
extraParams?: Record<string, unknown>,
|
|
72
|
+
): Record<string, unknown> {
|
|
73
|
+
const formatImage = (b64: string) =>
|
|
74
|
+
b64.startsWith("data:") ? b64 : `data:image/jpeg;base64,${b64}`;
|
|
75
|
+
|
|
76
|
+
return {
|
|
77
|
+
image_url: formatImage(sourceBase64),
|
|
78
|
+
second_image_url: formatImage(targetBase64),
|
|
79
|
+
...extraParams,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// =============================================================================
|
|
84
|
+
// FEATURE-SPECIFIC BUILDERS
|
|
85
|
+
// =============================================================================
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Build upscale input for FAL clarity-upscaler
|
|
89
|
+
*/
|
|
90
|
+
export function buildUpscaleInput(
|
|
91
|
+
base64: string,
|
|
92
|
+
options?: UpscaleOptions,
|
|
93
|
+
): Record<string, unknown> {
|
|
94
|
+
return buildSingleImageInput(base64, {
|
|
95
|
+
scale: options?.scaleFactor || 2,
|
|
96
|
+
face_enhance: options?.enhanceFaces || false,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Build photo restore input for FAL aura-sr
|
|
102
|
+
*/
|
|
103
|
+
export function buildPhotoRestoreInput(
|
|
104
|
+
base64: string,
|
|
105
|
+
options?: PhotoRestoreOptions,
|
|
106
|
+
): Record<string, unknown> {
|
|
107
|
+
return buildSingleImageInput(base64, {
|
|
108
|
+
face_enhance: options?.enhanceFaces || true,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Build AI hug/kiss video input for FAL wan-25-preview
|
|
114
|
+
* Supports dual images for interaction videos
|
|
115
|
+
*/
|
|
116
|
+
export function buildVideoFromImageInput(
|
|
117
|
+
base64: string,
|
|
118
|
+
options?: VideoFromImageOptions,
|
|
119
|
+
): Record<string, unknown> {
|
|
120
|
+
const params: Record<string, unknown> = {
|
|
121
|
+
motion_prompt: options?.motion_prompt,
|
|
122
|
+
num_frames: options?.duration ? Math.ceil(options.duration * 24) : undefined,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// If target image is provided, use dual image format
|
|
126
|
+
if (options?.target_image) {
|
|
127
|
+
return buildDualImageInput(base64, options.target_image, params);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return buildSingleImageInput(base64, params);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Build face swap input for FAL face-swap
|
|
135
|
+
*/
|
|
136
|
+
export function buildFaceSwapInput(
|
|
137
|
+
sourceBase64: string,
|
|
138
|
+
targetBase64: string,
|
|
139
|
+
_options?: FaceSwapOptions,
|
|
140
|
+
): Record<string, unknown> {
|
|
141
|
+
return buildDualImageInput(sourceBase64, targetBase64);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Build anime selfie input for FAL anime-image-generator
|
|
146
|
+
*/
|
|
147
|
+
export function buildAnimeSelfieInput(
|
|
148
|
+
base64: string,
|
|
149
|
+
options?: AnimeSelfieOptions,
|
|
150
|
+
): Record<string, unknown> {
|
|
151
|
+
return buildSingleImageInput(base64, {
|
|
152
|
+
style: options?.style || "anime",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Build remove background input for FAL bria/background/remove
|
|
158
|
+
*/
|
|
159
|
+
export function buildRemoveBackgroundInput(
|
|
160
|
+
base64: string,
|
|
161
|
+
_options?: RemoveBackgroundOptions,
|
|
162
|
+
): Record<string, unknown> {
|
|
163
|
+
return buildSingleImageInput(base64);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Build remove object (inpaint) input for FAL flux-kontext-lora/inpaint
|
|
168
|
+
*/
|
|
169
|
+
export function buildRemoveObjectInput(
|
|
170
|
+
base64: string,
|
|
171
|
+
options?: RemoveObjectOptions,
|
|
172
|
+
): Record<string, unknown> {
|
|
173
|
+
return buildSingleImageInput(base64, {
|
|
174
|
+
mask_url: options?.mask,
|
|
175
|
+
prompt: options?.prompt || "Remove the object and fill with background",
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Build replace background input for FAL bria/background/replace
|
|
181
|
+
*/
|
|
182
|
+
export function buildReplaceBackgroundInput(
|
|
183
|
+
base64: string,
|
|
184
|
+
options: ReplaceBackgroundOptions,
|
|
185
|
+
): Record<string, unknown> {
|
|
186
|
+
return buildSingleImageInput(base64, {
|
|
187
|
+
prompt: options.prompt,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Build HD touch up input (same as upscale)
|
|
193
|
+
*/
|
|
194
|
+
export function buildHDTouchUpInput(
|
|
195
|
+
base64: string,
|
|
196
|
+
options?: UpscaleOptions,
|
|
197
|
+
): Record<string, unknown> {
|
|
198
|
+
return buildUpscaleInput(base64, options);
|
|
199
|
+
}
|