@umituz/react-native-ai-fal-provider 2.0.17 → 2.0.19

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.
Files changed (28) hide show
  1. package/package.json +1 -1
  2. package/src/domain/types/input-builders.types.ts +1 -1
  3. package/src/domain/types/provider.types.ts +1 -2
  4. package/src/exports/infrastructure.ts +0 -10
  5. package/src/infrastructure/services/fal-provider-subscription.ts +11 -60
  6. package/src/infrastructure/services/fal-provider.constants.ts +2 -11
  7. package/src/infrastructure/services/fal-provider.ts +25 -35
  8. package/src/infrastructure/services/fal-queue-operations.ts +10 -7
  9. package/src/infrastructure/services/fal-status-mapper.ts +7 -6
  10. package/src/infrastructure/services/request-store.ts +9 -15
  11. package/src/infrastructure/utils/cost-tracker.ts +2 -9
  12. package/src/infrastructure/utils/cost-tracking-executor.util.ts +3 -6
  13. package/src/infrastructure/utils/error-mapper.ts +0 -7
  14. package/src/infrastructure/utils/fal-storage.util.ts +14 -26
  15. package/src/infrastructure/utils/index.ts +0 -10
  16. package/src/infrastructure/utils/input-builders.util.ts +0 -2
  17. package/src/infrastructure/utils/input-preprocessor.util.ts +4 -34
  18. package/src/infrastructure/utils/input-validator.util.ts +16 -10
  19. package/src/infrastructure/utils/type-guards.util.ts +4 -3
  20. package/src/infrastructure/validators/nsfw-validator.ts +0 -17
  21. package/src/init/createAiProviderInitModule.ts +2 -36
  22. package/src/presentation/hooks/use-fal-generation.ts +0 -5
  23. package/src/presentation/hooks/use-models.ts +1 -11
  24. package/src/infrastructure/builders/image-feature-builder.ts +0 -64
  25. package/src/infrastructure/builders/video-feature-builder.ts +0 -52
  26. package/src/infrastructure/services/fal-feature-models.ts +0 -48
  27. package/src/infrastructure/utils/image-feature-builders.util.ts +0 -103
  28. package/src/infrastructure/utils/video-feature-builders.util.ts +0 -58
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-fal-provider",
3
- "version": "2.0.17",
3
+ "version": "2.0.19",
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",
@@ -55,5 +55,5 @@ export interface TextToVideoOptions {
55
55
  }
56
56
 
57
57
  export interface FaceSwapOptions {
58
- // No additional options
58
+ readonly enhanceFaces?: boolean;
59
59
  }
@@ -39,8 +39,6 @@ export interface AIProviderConfig {
39
39
  textToImageModel?: string;
40
40
  imageEditModel?: string;
41
41
  videoGenerationModel?: string;
42
- videoFeatureModels?: Partial<Record<VideoFeatureType, string>>;
43
- imageFeatureModels?: Partial<Record<ImageFeatureType, string>>;
44
42
  }
45
43
 
46
44
  // =============================================================================
@@ -93,6 +91,7 @@ export interface SubscribeOptions<T = unknown> {
93
91
 
94
92
  export interface RunOptions {
95
93
  onProgress?: (progress: ProviderProgressInfo) => void;
94
+ signal?: AbortSignal;
96
95
  }
97
96
 
98
97
  // =============================================================================
@@ -14,20 +14,10 @@ export type { FalProviderType } from "../infrastructure/services";
14
14
 
15
15
  export {
16
16
  categorizeFalError,
17
- falErrorMapper,
18
17
  mapFalError,
19
18
  isFalErrorRetryable,
20
19
  buildSingleImageInput,
21
20
  buildDualImageInput,
22
- buildUpscaleInput,
23
- buildPhotoRestoreInput,
24
- buildVideoFromImageInput,
25
- buildFaceSwapInput,
26
- buildImageToImageInput,
27
- buildRemoveBackgroundInput,
28
- buildRemoveObjectInput,
29
- buildReplaceBackgroundInput,
30
- buildHDTouchUpInput,
31
21
  } from "../infrastructure/utils";
32
22
 
33
23
  export { CostTracker } from "../infrastructure/utils/cost-tracker";
@@ -11,8 +11,6 @@ import { mapFalStatusToJobStatus } from "./fal-status-mapper";
11
11
  import { validateNSFWContent } from "../validators/nsfw-validator";
12
12
  import { NSFWContentError } from "./nsfw-content-error";
13
13
 
14
- declare const __DEV__: boolean | undefined;
15
-
16
14
  interface FalApiErrorDetail {
17
15
  msg?: string;
18
16
  type?: string;
@@ -59,22 +57,16 @@ export async function handleFalSubscription<T = unknown>(
59
57
  signal?: AbortSignal
60
58
  ): Promise<{ result: T; requestId: string | null }> {
61
59
  const timeoutMs = options?.timeoutMs ?? DEFAULT_FAL_CONFIG.defaultTimeoutMs;
60
+
61
+ if (timeoutMs <= 0 || timeoutMs > 3600000) {
62
+ throw new Error(`Invalid timeout: ${timeoutMs}ms. Must be between 1 and 3600000ms (1 hour)`);
63
+ }
64
+
62
65
  let timeoutId: ReturnType<typeof setTimeout> | null = null;
63
66
  let currentRequestId: string | null = null;
64
67
  let abortHandler: (() => void) | null = null;
68
+ let listenerAdded = false;
65
69
 
66
- if (typeof __DEV__ !== "undefined" && __DEV__) {
67
- console.log("[FalProvider] Subscribe started:", {
68
- model,
69
- timeoutMs,
70
- inputKeys: Object.keys(input),
71
- hasImageUrl: !!input.image_url,
72
- hasPrompt: !!input.prompt,
73
- promptPreview: input.prompt ? String(input.prompt).substring(0, 50) + "..." : "N/A",
74
- });
75
- }
76
-
77
- // Check if already aborted before starting
78
70
  if (signal?.aborted) {
79
71
  throw new Error("Request cancelled by user");
80
72
  }
@@ -82,7 +74,6 @@ export async function handleFalSubscription<T = unknown>(
82
74
  let lastStatus = "";
83
75
 
84
76
  try {
85
- // Create promises array conditionally to avoid unnecessary abort promise creation
86
77
  const promises: Promise<unknown>[] = [
87
78
  fal.subscribe(model, {
88
79
  input,
@@ -91,14 +82,13 @@ export async function handleFalSubscription<T = unknown>(
91
82
  onQueueUpdate: (update: { status: string; logs?: unknown[]; request_id?: string }) => {
92
83
  currentRequestId = update.request_id ?? currentRequestId;
93
84
  const jobStatus = mapFalStatusToJobStatus({
94
- ...update as unknown as FalQueueStatus,
85
+ status: update.status as FalQueueStatus["status"],
95
86
  requestId: currentRequestId ?? "",
87
+ logs: update.logs as FalQueueStatus["logs"],
88
+ queuePosition: undefined,
96
89
  });
97
90
  if (jobStatus.status !== lastStatus) {
98
91
  lastStatus = jobStatus.status;
99
- if (typeof __DEV__ !== "undefined" && __DEV__) {
100
- console.log("[FalProvider] Status:", jobStatus.status, "RequestId:", currentRequestId);
101
- }
102
92
  }
103
93
  options?.onQueueUpdate?.(jobStatus);
104
94
  },
@@ -110,55 +100,33 @@ export async function handleFalSubscription<T = unknown>(
110
100
  }),
111
101
  ];
112
102
 
113
- // Add abort promise only if signal is provided and not already aborted
114
103
  if (signal && !signal.aborted) {
115
104
  const abortPromise = new Promise<never>((_, reject) => {
116
105
  abortHandler = () => {
117
106
  reject(new Error("Request cancelled by user"));
118
107
  };
119
108
  signal.addEventListener("abort", abortHandler);
109
+ listenerAdded = true;
120
110
  });
121
111
  promises.push(abortPromise);
122
112
  }
123
113
 
124
114
  const result = await Promise.race(promises);
125
115
 
126
- if (typeof __DEV__ !== "undefined" && __DEV__) {
127
- console.log("[FalProvider] Subscribe completed:", {
128
- model,
129
- requestId: currentRequestId,
130
- resultKeys: result ? Object.keys(result as object) : "null",
131
- hasVideo: !!(result as Record<string, unknown>)?.video,
132
- hasOutput: !!(result as Record<string, unknown>)?.output,
133
- hasData: !!(result as Record<string, unknown>)?.data,
134
- });
135
- // Log full result structure for debugging
136
- console.log("[FalProvider] Result structure:", JSON.stringify(result, null, 2).substring(0, 1000));
137
- }
138
-
139
116
  validateNSFWContent(result as Record<string, unknown>);
140
117
 
141
118
  options?.onResult?.(result as T);
142
119
  return { result: result as T, requestId: currentRequestId };
143
120
  } catch (error) {
144
- // Preserve NSFWContentError type
145
121
  if (error instanceof NSFWContentError) {
146
- if (typeof __DEV__ !== "undefined" && __DEV__) {
147
- console.error("[FalProvider] NSFW content detected");
148
- }
149
122
  throw error;
150
123
  }
151
124
 
152
- // Parse FAL error and throw with user-friendly message
153
125
  const userMessage = parseFalError(error);
154
- if (typeof __DEV__ !== "undefined" && __DEV__) {
155
- console.error("[FalProvider] Error:", userMessage);
156
- }
157
126
  throw new Error(userMessage);
158
127
  } finally {
159
128
  if (timeoutId) clearTimeout(timeoutId);
160
- // Clean up abort listener to prevent memory leak (only if it was added)
161
- if (abortHandler && signal && !signal.aborted) {
129
+ if (listenerAdded && abortHandler && signal) {
162
130
  signal.removeEventListener("abort", abortHandler);
163
131
  }
164
132
  }
@@ -174,36 +142,19 @@ export async function handleFalRun<T = unknown>(
174
142
  ): Promise<T> {
175
143
  options?.onProgress?.({ progress: 10, status: "IN_PROGRESS" as const });
176
144
 
177
- if (typeof __DEV__ !== "undefined" && __DEV__) {
178
- console.log("[FalProvider] run() model:", model, "inputKeys:", Object.keys(input));
179
- }
180
-
181
145
  try {
182
146
  const result = await fal.run(model, { input });
183
147
 
184
- if (typeof __DEV__ !== "undefined" && __DEV__) {
185
- console.log("[FalProvider] run() raw result:", JSON.stringify(result, null, 2));
186
- console.log("[FalProvider] run() result type:", typeof result);
187
- console.log("[FalProvider] run() result keys:", result ? Object.keys(result as object) : "null");
188
- }
189
-
190
148
  validateNSFWContent(result as Record<string, unknown>);
191
149
 
192
150
  options?.onProgress?.({ progress: 100, status: "COMPLETED" as const });
193
151
  return result as T;
194
152
  } catch (error) {
195
- // Preserve NSFWContentError type
196
153
  if (error instanceof NSFWContentError) {
197
- if (typeof __DEV__ !== "undefined" && __DEV__) {
198
- console.error("[FalProvider] run() NSFW content detected");
199
- }
200
154
  throw error;
201
155
  }
202
156
 
203
157
  const userMessage = parseFalError(error);
204
- if (typeof __DEV__ !== "undefined" && __DEV__) {
205
- console.error("[FalProvider] run() Error:", userMessage);
206
- }
207
158
  throw new Error(userMessage);
208
159
  }
209
160
  }
@@ -14,17 +14,8 @@ export const DEFAULT_FAL_CONFIG = {
14
14
  } as const;
15
15
 
16
16
  export const FAL_CAPABILITIES: ProviderCapabilities = {
17
- imageFeatures: [
18
- "upscale",
19
- "photo-restore",
20
- "face-swap",
21
- "anime-selfie",
22
- "remove-background",
23
- "remove-object",
24
- "hd-touch-up",
25
- "replace-background",
26
- ] as const,
27
- videoFeatures: ["image-to-video", "text-to-video"] as const,
17
+ imageFeatures: [] as const,
18
+ videoFeatures: [] as const,
28
19
  textToImage: true,
29
20
  textToVideo: true,
30
21
  imageToVideo: true,
@@ -6,8 +6,8 @@
6
6
  import { fal } from "@fal-ai/client";
7
7
  import type {
8
8
  IAIProvider, AIProviderConfig, JobSubmission, JobStatus, SubscribeOptions,
9
- RunOptions, ImageFeatureType, VideoFeatureType, ImageFeatureInputData,
10
- VideoFeatureInputData, ProviderCapabilities,
9
+ RunOptions, ProviderCapabilities, ImageFeatureType, VideoFeatureType,
10
+ ImageFeatureInputData, VideoFeatureInputData,
11
11
  } from "../../domain/types";
12
12
  import type { CostTrackerConfig } from "../../domain/entities/cost-tracking.types";
13
13
  import { DEFAULT_FAL_CONFIG, FAL_CAPABILITIES } from "./fal-provider.constants";
@@ -18,11 +18,8 @@ import {
18
18
  removeRequest, cancelAllRequests, hasActiveRequests,
19
19
  } from "./request-store";
20
20
  import * as queueOps from "./fal-queue-operations";
21
- import * as featureModels from "./fal-feature-models";
22
21
  import { validateInput } from "../utils/input-validator.util";
23
22
 
24
- declare const __DEV__: boolean | undefined;
25
-
26
23
  export class FalProvider implements IAIProvider {
27
24
  readonly providerId = "fal";
28
25
  readonly providerName = "FAL AI";
@@ -30,13 +27,9 @@ export class FalProvider implements IAIProvider {
30
27
  private apiKey: string | null = null;
31
28
  private initialized = false;
32
29
  private costTracker: CostTracker | null = null;
33
- private videoFeatureModels: Record<string, string> = {};
34
- private imageFeatureModels: Record<string, string> = {};
35
30
 
36
31
  initialize(config: AIProviderConfig): void {
37
32
  this.apiKey = config.apiKey;
38
- this.videoFeatureModels = config.videoFeatureModels ?? {};
39
- this.imageFeatureModels = config.imageFeatureModels ?? {};
40
33
  fal.config({
41
34
  credentials: config.apiKey,
42
35
  retry: {
@@ -46,9 +39,6 @@ export class FalProvider implements IAIProvider {
46
39
  },
47
40
  });
48
41
  this.initialized = true;
49
- if (typeof __DEV__ !== "undefined" && __DEV__) {
50
- console.log("[FalProvider] Initialized");
51
- }
52
42
  }
53
43
 
54
44
  enableCostTracking(config?: CostTrackerConfig): void {
@@ -75,10 +65,24 @@ export class FalProvider implements IAIProvider {
75
65
  return FAL_CAPABILITIES;
76
66
  }
77
67
 
78
- isFeatureSupported(feature: ImageFeatureType | VideoFeatureType): boolean {
79
- const caps = this.getCapabilities();
80
- return caps.imageFeatures.includes(feature as ImageFeatureType) ||
81
- caps.videoFeatures.includes(feature as VideoFeatureType);
68
+ isFeatureSupported(_feature: ImageFeatureType | VideoFeatureType): boolean {
69
+ return false;
70
+ }
71
+
72
+ getImageFeatureModel(_feature: ImageFeatureType): string {
73
+ throw new Error("Feature-specific models are not supported in this provider. Use the main app's feature implementations.");
74
+ }
75
+
76
+ buildImageFeatureInput(_feature: ImageFeatureType, _data: ImageFeatureInputData): Record<string, unknown> {
77
+ throw new Error("Feature-specific input building is not supported in this provider. Use the main app's feature implementations.");
78
+ }
79
+
80
+ getVideoFeatureModel(_feature: VideoFeatureType): string {
81
+ throw new Error("Feature-specific models are not supported in this provider. Use the main app's feature implementations.");
82
+ }
83
+
84
+ buildVideoFeatureInput(_feature: VideoFeatureType, _data: VideoFeatureInputData): Record<string, unknown> {
85
+ throw new Error("Feature-specific input building is not supported in this provider. Use the main app's feature implementations.");
82
86
  }
83
87
 
84
88
  private validateInit(): void {
@@ -116,9 +120,6 @@ export class FalProvider implements IAIProvider {
116
120
 
117
121
  const existing = getExistingRequest<T>(key);
118
122
  if (existing) {
119
- if (typeof __DEV__ !== "undefined" && __DEV__) {
120
- console.log(`[FalProvider] Dedup: returning existing promise for ${model}`);
121
- }
122
123
  return existing.promise;
123
124
  }
124
125
 
@@ -142,6 +143,11 @@ export class FalProvider implements IAIProvider {
142
143
  validateInput(model, input);
143
144
  const processedInput = await preprocessInput(input);
144
145
 
146
+ const signal = options?.signal;
147
+ if (signal?.aborted) {
148
+ throw new Error("Request cancelled by user");
149
+ }
150
+
145
151
  return executeWithCostTracking({
146
152
  tracker: this.costTracker,
147
153
  model,
@@ -163,22 +169,6 @@ export class FalProvider implements IAIProvider {
163
169
  hasRunningRequest(): boolean {
164
170
  return hasActiveRequests();
165
171
  }
166
-
167
- getImageFeatureModel(feature: ImageFeatureType): string {
168
- return featureModels.getImageFeatureModel(this.imageFeatureModels, feature);
169
- }
170
-
171
- buildImageFeatureInput(feature: ImageFeatureType, data: ImageFeatureInputData): Record<string, unknown> {
172
- return featureModels.buildImageFeatureInput(feature, data);
173
- }
174
-
175
- getVideoFeatureModel(feature: VideoFeatureType): string {
176
- return featureModels.getVideoFeatureModel(this.videoFeatureModels, feature);
177
- }
178
-
179
- buildVideoFeatureInput(feature: VideoFeatureType, data: VideoFeatureInputData): Record<string, unknown> {
180
- return featureModels.buildVideoFeatureInput(feature, data);
181
- }
182
172
  }
183
173
 
184
174
  export const falProvider = new FalProvider();
@@ -37,14 +37,10 @@ export async function submitJob(model: string, input: Record<string, unknown>):
37
37
  export async function getJobStatus(model: string, requestId: string): Promise<JobStatus> {
38
38
  const status = await fal.queue.status(model, { requestId, logs: true });
39
39
 
40
- // Validate the response structure before mapping
41
40
  if (!isValidFalQueueStatus(status)) {
42
- // Fallback to default status if validation fails
43
- return {
44
- status: "IN_PROGRESS",
45
- logs: [],
46
- queuePosition: undefined,
47
- };
41
+ throw new Error(
42
+ `Invalid FAL queue status response for model ${model}, requestId ${requestId}`
43
+ );
48
44
  }
49
45
 
50
46
  return mapFalStatusToJobStatus(status);
@@ -52,5 +48,12 @@ export async function getJobStatus(model: string, requestId: string): Promise<Jo
52
48
 
53
49
  export async function getJobResult<T = unknown>(model: string, requestId: string): Promise<T> {
54
50
  const result = await fal.queue.result(model, { requestId });
51
+
52
+ if (!result || typeof result !== 'object') {
53
+ throw new Error(
54
+ `Invalid FAL queue result for model ${model}, requestId ${requestId}`
55
+ );
56
+ }
57
+
55
58
  return result.data as T;
56
59
  }
@@ -24,13 +24,14 @@ export function mapFalStatusToJobStatus(status: FalQueueStatus): JobStatus {
24
24
 
25
25
  return {
26
26
  status: mappedStatus,
27
- logs: status.logs?.map((log: FalLogEntry) => ({
28
- message: log.message,
29
- level: log.level ?? "info",
30
- timestamp: log.timestamp ?? new Date().toISOString(),
31
- })) ?? [],
27
+ logs: Array.isArray(status.logs)
28
+ ? status.logs.map((log: FalLogEntry) => ({
29
+ message: log.message,
30
+ level: log.level ?? "info",
31
+ timestamp: log.timestamp ?? new Date().toISOString(),
32
+ }))
33
+ : [],
32
34
  queuePosition: status.queuePosition ?? undefined,
33
- // Preserve requestId from FalQueueStatus for use in hooks
34
35
  requestId: status.requestId,
35
36
  };
36
37
  }
@@ -3,8 +3,6 @@
3
3
  * Survives hot reloads for React Native development
4
4
  */
5
5
 
6
- declare const __DEV__: boolean | undefined;
7
-
8
6
  export interface ActiveRequest<T = unknown> {
9
7
  promise: Promise<T>;
10
8
  abortController: AbortController;
@@ -13,9 +11,6 @@ export interface ActiveRequest<T = unknown> {
13
11
  const STORE_KEY = "__FAL_PROVIDER_REQUESTS__";
14
12
  type RequestStore = Map<string, ActiveRequest>;
15
13
 
16
- // Counter for generating unique request IDs
17
- let requestCounter = 0;
18
-
19
14
  export function getRequestStore(): RequestStore {
20
15
  if (!(globalThis as Record<string, unknown>)[STORE_KEY]) {
21
16
  (globalThis as Record<string, unknown>)[STORE_KEY] = new Map();
@@ -27,18 +22,20 @@ export function getRequestStore(): RequestStore {
27
22
  * Create a collision-resistant request key using combination of:
28
23
  * - Model name
29
24
  * - Input hash (for quick comparison)
30
- * - Unique counter (guarantees uniqueness)
25
+ * - Unique ID (guarantees uniqueness)
31
26
  */
32
27
  export function createRequestKey(model: string, input: Record<string, unknown>): string {
33
28
  const inputStr = JSON.stringify(input, Object.keys(input).sort());
34
- // Use DJB2 hash for input fingerprinting (faster than crypto for dedup check)
29
+ // Use DJB2 hash for input fingerprinting
35
30
  let hash = 0;
36
31
  for (let i = 0; i < inputStr.length; i++) {
37
32
  const char = inputStr.charCodeAt(i);
38
33
  hash = ((hash << 5) - hash + char) | 0;
39
34
  }
40
- // Add counter to guarantee uniqueness even with hash collisions
41
- const uniqueId = `${requestCounter++}`;
35
+ // Use crypto.randomUUID() for guaranteed uniqueness without race conditions
36
+ const uniqueId = typeof crypto !== 'undefined' && crypto.randomUUID
37
+ ? crypto.randomUUID()
38
+ : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
42
39
  return `${model}:${hash.toString(36)}:${uniqueId}`;
43
40
  }
44
41
 
@@ -56,10 +53,7 @@ export function removeRequest(key: string): void {
56
53
 
57
54
  export function cancelAllRequests(): void {
58
55
  const store = getRequestStore();
59
- store.forEach((req, key) => {
60
- if (typeof __DEV__ !== "undefined" && __DEV__) {
61
- console.log(`[RequestStore] Cancelling: ${key}`);
62
- }
56
+ store.forEach((req) => {
63
57
  req.abortController.abort();
64
58
  });
65
59
  store.clear();
@@ -81,7 +75,7 @@ export function cleanupRequestStore(_maxAge: number = 300000): void {
81
75
 
82
76
  // Requests are automatically removed when they complete (via finally block)
83
77
  // This function exists for future enhancements like time-based cleanup
84
- if (store.size > 50 && typeof __DEV__ !== "undefined" && __DEV__) {
85
- console.warn(`[RequestStore] Large request store size: ${store.size}. Consider investigating potential leaks.`);
78
+ if (store.size > 50) {
79
+ // Store size exceeds threshold - indicates potential memory leak
86
80
  }
87
81
  }
@@ -10,8 +10,6 @@ import type {
10
10
  } from "../../domain/entities/cost-tracking.types";
11
11
  import { findModelById } from "../../domain/constants/default-models.constants";
12
12
 
13
- declare const __DEV__: boolean | undefined;
14
-
15
13
  interface CostSummary {
16
14
  totalEstimatedCost: number;
17
15
  totalActualCost: number;
@@ -69,14 +67,8 @@ export class CostTracker {
69
67
  currency: this.config.currency,
70
68
  };
71
69
  }
72
-
73
- if (typeof __DEV__ !== "undefined" && __DEV__) {
74
- console.warn("[CostTracker] No pricing found for model:", modelId);
75
- }
76
70
  } catch (error) {
77
- if (typeof __DEV__ !== "undefined" && __DEV__) {
78
- console.warn("[CostTracker] Error finding model:", modelId, error);
79
- }
71
+ // Silently return default cost info on error
80
72
  }
81
73
 
82
74
  return {
@@ -164,6 +156,7 @@ export class CostTracker {
164
156
  clearHistory(): void {
165
157
  this.costHistory = [];
166
158
  this.currentOperationCosts.clear();
159
+ this.operationCounter = 0;
167
160
  }
168
161
 
169
162
  getCostsByModel(modelId: string): GenerationCost[] {
@@ -5,8 +5,6 @@
5
5
 
6
6
  import type { CostTracker } from "./cost-tracker";
7
7
 
8
- declare const __DEV__: boolean | undefined;
9
-
10
8
  interface ExecuteWithCostTrackingOptions<T> {
11
9
  tracker: CostTracker | null;
12
10
  model: string;
@@ -37,9 +35,8 @@ export async function executeWithCostTracking<T>(
37
35
  const requestId = getRequestId?.(result);
38
36
  tracker.completeOperation(operationId, model, operation, requestId);
39
37
  } catch (costError) {
40
- if (typeof __DEV__ !== "undefined" && __DEV__) {
41
- console.warn("[CostTracking] Failed:", costError);
42
- }
38
+ // Cost tracking failure shouldn't break the operation
39
+ // Log for debugging but don't throw
43
40
  }
44
41
 
45
42
  return result;
@@ -47,7 +44,7 @@ export async function executeWithCostTracking<T>(
47
44
  try {
48
45
  tracker.failOperation(operationId);
49
46
  } catch {
50
- // Silently ignore cost tracking errors on failure path
47
+ // Cost tracking cleanup failure on error path - ignore
51
48
  }
52
49
  throw error;
53
50
  }
@@ -42,10 +42,3 @@ export function mapFalError(error: unknown): FalErrorInfo {
42
42
  export function isFalErrorRetryable(error: unknown): boolean {
43
43
  return categorizeFalError(error).retryable;
44
44
  }
45
-
46
- // Backward compatibility
47
- export const falErrorMapper = {
48
- mapToErrorInfo: mapFalError,
49
- isRetryable: isFalErrorRetryable,
50
- getErrorType: (error: unknown) => categorizeFalError(error).type,
51
- };
@@ -7,12 +7,8 @@ import { fal } from "@fal-ai/client";
7
7
  import {
8
8
  base64ToTempFile,
9
9
  deleteTempFile,
10
- getFileSize,
11
- detectMimeType,
12
10
  } from "@umituz/react-native-design-system/filesystem";
13
11
 
14
- declare const __DEV__: boolean | undefined;
15
-
16
12
  /**
17
13
  * Upload base64 image to FAL storage
18
14
  * Uses design system's filesystem utilities for React Native compatibility
@@ -20,30 +16,22 @@ declare const __DEV__: boolean | undefined;
20
16
  export async function uploadToFalStorage(base64: string): Promise<string> {
21
17
  // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-assignment
22
18
  const tempUri = (await base64ToTempFile(base64));
23
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
24
- const fileSize = getFileSize(tempUri);
25
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
26
- const mimeType = detectMimeType(base64);
27
-
28
- if (typeof __DEV__ !== "undefined" && __DEV__) {
29
- console.log("[FalStorage] Uploading image", {
30
- size: `${(fileSize / 1024).toFixed(1)}KB`,
31
- type: mimeType,
32
- });
33
- }
34
19
 
35
- const response = await fetch(tempUri);
36
- const blob = await response.blob();
37
- const url = await fal.storage.upload(blob);
38
-
39
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call
40
- await deleteTempFile(tempUri);
41
-
42
- if (typeof __DEV__ !== "undefined" && __DEV__) {
43
- console.log("[FalStorage] Upload complete", { url: url.slice(0, 60) + "..." });
20
+ try {
21
+ const response = await fetch(tempUri);
22
+ const blob = await response.blob();
23
+ const url = await fal.storage.upload(blob);
24
+ return url;
25
+ } finally {
26
+ if (tempUri) {
27
+ try {
28
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-call
29
+ await deleteTempFile(tempUri);
30
+ } catch {
31
+ // Silently ignore cleanup failures
32
+ }
33
+ }
44
34
  }
45
-
46
- return url;
47
35
  }
48
36
 
49
37
  /**
@@ -4,7 +4,6 @@
4
4
 
5
5
  export { categorizeFalError } from "./error-categorizer";
6
6
  export {
7
- falErrorMapper,
8
7
  mapFalError,
9
8
  isFalErrorRetryable,
10
9
  } from "./error-mapper";
@@ -12,15 +11,6 @@ export {
12
11
  export {
13
12
  buildSingleImageInput,
14
13
  buildDualImageInput,
15
- buildUpscaleInput,
16
- buildPhotoRestoreInput,
17
- buildVideoFromImageInput,
18
- buildFaceSwapInput,
19
- buildImageToImageInput,
20
- buildRemoveBackgroundInput,
21
- buildRemoveObjectInput,
22
- buildReplaceBackgroundInput,
23
- buildHDTouchUpInput,
24
14
  } from "./input-builders.util";
25
15
 
26
16
  export {
@@ -4,5 +4,3 @@
4
4
  */
5
5
 
6
6
  export * from "./base-builders.util";
7
- export * from "./image-feature-builders.util";
8
- export * from "./video-feature-builders.util";
@@ -5,8 +5,6 @@
5
5
 
6
6
  import { uploadToFalStorage } from "./fal-storage.util";
7
7
 
8
- declare const __DEV__: boolean | undefined;
9
-
10
8
  const IMAGE_URL_KEYS = [
11
9
  "image_url",
12
10
  "second_image_url",
@@ -37,23 +35,11 @@ export async function preprocessInput(
37
35
  for (const key of IMAGE_URL_KEYS) {
38
36
  const value = result[key];
39
37
  if (isBase64DataUri(value)) {
40
- if (typeof __DEV__ !== "undefined" && __DEV__) {
41
- console.log(`[FalPreprocessor] Uploading ${key} to storage...`);
42
- }
43
-
44
38
  const uploadPromise = uploadToFalStorage(value)
45
39
  .then((url) => {
46
40
  result[key] = url;
47
- if (typeof __DEV__ !== "undefined" && __DEV__) {
48
- console.log(`[FalPreprocessor] ${key} uploaded`, {
49
- url: url.slice(0, 50) + "...",
50
- });
51
- }
52
41
  })
53
42
  .catch((error) => {
54
- if (typeof __DEV__ !== "undefined" && __DEV__) {
55
- console.error(`[FalPreprocessor] Failed to upload ${key}:`, error);
56
- }
57
43
  throw new Error(`Failed to upload ${key}: ${error instanceof Error ? error.message : "Unknown error"}`);
58
44
  });
59
45
 
@@ -62,52 +48,36 @@ export async function preprocessInput(
62
48
  }
63
49
 
64
50
  // Handle image_urls array (for multi-person generation)
65
- if (Array.isArray(result.image_urls)) {
51
+ if (Array.isArray(result.image_urls) && result.image_urls.length > 0) {
66
52
  const imageUrls = result.image_urls as unknown[];
67
- // Pre-initialize array with correct length to avoid sparse array
68
- const processedUrls: string[] = new Array(imageUrls.length).fill("") as string[];
53
+ const processedUrls: string[] = [];
69
54
 
70
55
  for (let i = 0; i < imageUrls.length; i++) {
71
56
  const imageUrl = imageUrls[i];
72
57
  if (isBase64DataUri(imageUrl)) {
73
- if (typeof __DEV__ !== "undefined" && __DEV__) {
74
- console.log(`[FalPreprocessor] Uploading image_urls[${i}] to storage...`);
75
- }
76
-
77
- // Capture index in closure to ensure correct assignment
78
58
  const index = i;
79
59
  const uploadPromise = uploadToFalStorage(imageUrl)
80
60
  .then((url) => {
81
61
  processedUrls[index] = url;
82
- if (typeof __DEV__ !== "undefined" && __DEV__) {
83
- console.log(`[FalPreprocessor] image_urls[${index}] uploaded`, {
84
- url: url.slice(0, 50) + "...",
85
- });
86
- }
87
62
  })
88
63
  .catch((error) => {
89
- if (typeof __DEV__ !== "undefined" && __DEV__) {
90
- console.error(`[FalPreprocessor] Failed to upload image_urls[${index}]:`, error);
91
- }
92
64
  throw new Error(`Failed to upload image_urls[${index}]: ${error instanceof Error ? error.message : "Unknown error"}`);
93
65
  });
94
66
 
95
67
  uploadPromises.push(uploadPromise);
96
68
  } else if (typeof imageUrl === "string") {
97
69
  processedUrls[i] = imageUrl;
70
+ } else {
71
+ processedUrls[i] = "";
98
72
  }
99
73
  }
100
74
 
101
- // Always set processed URLs after all uploads complete
102
75
  result.image_urls = processedUrls;
103
76
  }
104
77
 
105
78
  // Wait for ALL uploads to complete (both individual keys and array)
106
79
  if (uploadPromises.length > 0) {
107
80
  await Promise.all(uploadPromises);
108
- if (typeof __DEV__ !== "undefined" && __DEV__) {
109
- console.log(`[FalPreprocessor] All images uploaded (${uploadPromises.length})`);
110
- }
111
81
  }
112
82
 
113
83
  return result;
@@ -5,8 +5,6 @@
5
5
 
6
6
  import { isValidModelId, isValidPrompt } from "./type-guards.util";
7
7
 
8
- declare const __DEV__: boolean | undefined;
9
-
10
8
  export interface ValidationError {
11
9
  field: string;
12
10
  message: string;
@@ -75,18 +73,26 @@ export function validateInput(
75
73
 
76
74
  for (const field of imageFields) {
77
75
  const value = input[field];
78
- if (value !== undefined && typeof value !== "string") {
79
- errors.push({
80
- field,
81
- message: `${field} must be a string`,
82
- });
76
+ if (value !== undefined) {
77
+ if (typeof value !== "string") {
78
+ errors.push({
79
+ field,
80
+ message: `${field} must be a string`,
81
+ });
82
+ } else if (value.length > 0) {
83
+ const isValidUrl = value.startsWith('http://') || value.startsWith('https://');
84
+ const isValidBase64 = value.startsWith('data:image/');
85
+ if (!isValidUrl && !isValidBase64) {
86
+ errors.push({
87
+ field,
88
+ message: `${field} must be a valid URL or base64 data URI`,
89
+ });
90
+ }
91
+ }
83
92
  }
84
93
  }
85
94
 
86
95
  if (errors.length > 0) {
87
- if (typeof __DEV__ !== "undefined" && __DEV__) {
88
- console.warn("[InputValidator] Validation errors:", errors);
89
- }
90
96
  throw new InputValidationError(errors);
91
97
  }
92
98
  }
@@ -92,9 +92,10 @@ export function isValidModelId(value: unknown): boolean {
92
92
  return false;
93
93
  }
94
94
 
95
- // FAL model IDs typically follow the pattern: "owner/model-name" or "owner/model/version"
96
- const modelIdPattern = /^[a-z0-9-]+\/[a-z0-9-]+(\/[a-z0-9.]+)?$/;
97
- return modelIdPattern.test(value);
95
+ // FAL model IDs follow pattern: "owner/model-name" or "owner/model/version"
96
+ // Allow uppercase, dots, underscores, hyphens
97
+ const modelIdPattern = /^[a-zA-Z0-9-_]+\/[a-zA-Z0-9-_.]+(\/[a-zA-Z0-9-_.]+)?$/;
98
+ return modelIdPattern.test(value) && value.length >= 3;
98
99
  }
99
100
 
100
101
  /**
@@ -5,8 +5,6 @@
5
5
 
6
6
  import { NSFWContentError } from "../services/nsfw-content-error";
7
7
 
8
- declare const __DEV__: boolean | undefined;
9
-
10
8
  /**
11
9
  * Check if value indicates NSFW content
12
10
  */
@@ -33,9 +31,6 @@ export function validateNSFWContent(result: Record<string, unknown>): void {
33
31
  if (nsfwConcepts && Array.isArray(nsfwConcepts)) {
34
32
  const hasNSFW = nsfwConcepts.some((value) => value === true);
35
33
  if (hasNSFW) {
36
- if (typeof __DEV__ !== "undefined" && __DEV__) {
37
- console.log("[FalProvider] NSFW content detected (has_nsfw_concepts), rejecting result");
38
- }
39
34
  throw new NSFWContentError();
40
35
  }
41
36
  }
@@ -43,27 +38,18 @@ export function validateNSFWContent(result: Record<string, unknown>): void {
43
38
  // Format 2: nsfw_detected boolean
44
39
  const nsfwDetected = result?.nsfw_detected;
45
40
  if (nsfwDetected !== undefined && isNSFWIndicator(nsfwDetected)) {
46
- if (typeof __DEV__ !== "undefined" && __DEV__) {
47
- console.log("[FalProvider] NSFW content detected (nsfw_detected), rejecting result");
48
- }
49
41
  throw new NSFWContentError();
50
42
  }
51
43
 
52
44
  // Format 3: is_nsfw boolean
53
45
  const isNsfw = result?.is_nsfw;
54
46
  if (isNsfw !== undefined && isNSFWIndicator(isNsfw)) {
55
- if (typeof __DEV__ !== "undefined" && __DEV__) {
56
- console.log("[FalProvider] NSFW content detected (is_nsfw), rejecting result");
57
- }
58
47
  throw new NSFWContentError();
59
48
  }
60
49
 
61
50
  // Format 4: nsfw_score number (> 0.5 threshold)
62
51
  const nsfwScore = result?.nsfw_score as number | undefined;
63
52
  if (typeof nsfwScore === "number" && nsfwScore > 0.5) {
64
- if (typeof __DEV__ !== "undefined" && __DEV__) {
65
- console.log("[FalProvider] NSFW content detected (nsfw_score: " + nsfwScore + "), rejecting result");
66
- }
67
53
  throw new NSFWContentError();
68
54
  }
69
55
 
@@ -72,9 +58,6 @@ export function validateNSFWContent(result: Record<string, unknown>): void {
72
58
  if (policyViolation && typeof policyViolation === "object") {
73
59
  const type = (policyViolation.type || "").toLowerCase();
74
60
  if (type.includes("nsfw") || type.includes("adult") || type.includes("explicit")) {
75
- if (typeof __DEV__ !== "undefined" && __DEV__) {
76
- console.log("[FalProvider] Content policy violation detected:", policyViolation);
77
- }
78
61
  throw new NSFWContentError();
79
62
  }
80
63
  }
@@ -5,8 +5,6 @@
5
5
 
6
6
  import { falProvider } from '../infrastructure/services';
7
7
 
8
- declare const __DEV__: boolean;
9
-
10
8
  /**
11
9
  * InitModule interface (from @umituz/react-native-design-system)
12
10
  */
@@ -24,18 +22,6 @@ export interface AiProviderInitModuleConfig {
24
22
  */
25
23
  getApiKey: () => string | undefined;
26
24
 
27
- /**
28
- * Video feature models mapping
29
- * Maps feature types to FAL model IDs
30
- */
31
- videoFeatureModels?: Record<string, string>;
32
-
33
- /**
34
- * Image feature models mapping
35
- * Maps feature types to FAL model IDs
36
- */
37
- imageFeatureModels?: Record<string, string>;
38
-
39
25
  /**
40
26
  * Whether this module is critical for app startup
41
27
  * @default false
@@ -63,10 +49,6 @@ export interface AiProviderInitModuleConfig {
63
49
  * createFirebaseInitModule(),
64
50
  * createAiProviderInitModule({
65
51
  * getApiKey: () => getFalApiKey(),
66
- * videoFeatureModels: {
67
- * "image-to-video": "fal-ai/wan-25-preview/image-to-video",
68
- * "text-to-video": "fal-ai/wan-25-preview/text-to-video",
69
- * },
70
52
  * }),
71
53
  * ],
72
54
  * });
@@ -77,8 +59,6 @@ export function createAiProviderInitModule(
77
59
  ): InitModule {
78
60
  const {
79
61
  getApiKey,
80
- videoFeatureModels,
81
- imageFeatureModels,
82
62
  critical = false,
83
63
  dependsOn = ['firebase'],
84
64
  } = config;
@@ -92,30 +72,16 @@ export function createAiProviderInitModule(
92
72
  const apiKey = getApiKey();
93
73
 
94
74
  if (!apiKey) {
95
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
96
- console.log('[createAiProviderInitModule] No API key - skipping');
97
- }
98
- return Promise.resolve(true); // Not an error, just skip
75
+ return Promise.resolve(false);
99
76
  }
100
77
 
101
- // Initialize FAL provider
102
78
  falProvider.initialize({
103
79
  apiKey,
104
- videoFeatureModels,
105
- imageFeatureModels,
106
80
  });
107
81
 
108
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
109
- console.log('[createAiProviderInitModule] FAL provider initialized');
110
- }
111
-
112
82
  return Promise.resolve(true);
113
83
  } catch (error) {
114
- if (typeof __DEV__ !== 'undefined' && __DEV__) {
115
- console.error('[createAiProviderInitModule] Error:', error);
116
- }
117
- // Continue on error - AI provider is not critical
118
- return Promise.resolve(true);
84
+ return Promise.resolve(false);
119
85
  }
120
86
  },
121
87
  };
@@ -52,11 +52,9 @@ export function useFalGeneration<T = unknown>(
52
52
  const result = await falProvider.subscribe<T>(modelEndpoint, input, {
53
53
  timeoutMs: options?.timeoutMs,
54
54
  onQueueUpdate: (status) => {
55
- // Update requestId ref when we receive it from status
56
55
  if (status.requestId) {
57
56
  currentRequestIdRef.current = status.requestId;
58
57
  }
59
- // Map JobStatus to FalQueueStatus for backward compatibility
60
58
  options?.onProgress?.({
61
59
  status: status.status,
62
60
  requestId: status.requestId ?? currentRequestIdRef.current ?? "",
@@ -95,9 +93,6 @@ export function useFalGeneration<T = unknown>(
95
93
  if (falProvider.hasRunningRequest()) {
96
94
  setIsCancelling(true);
97
95
  falProvider.cancelCurrentRequest();
98
- if (typeof __DEV__ !== "undefined" && __DEV__) {
99
- console.log("[useFalGeneration] Request cancelled");
100
- }
101
96
  }
102
97
  }, []);
103
98
 
@@ -19,7 +19,7 @@ import type {
19
19
  UseModelsReturn,
20
20
  } from "../../domain/types/model-selection.types";
21
21
 
22
- declare const __DEV__: boolean;
22
+ export type { UseModelsReturn } from "../../domain/types/model-selection.types";
23
23
 
24
24
  export interface UseModelsProps {
25
25
  /** Model type to fetch */
@@ -56,11 +56,6 @@ export function useModels(props: UseModelsProps): UseModelsReturn {
56
56
  setSelectedModel(initial);
57
57
  }
58
58
 
59
- if (typeof __DEV__ !== "undefined" && __DEV__) {
60
- // eslint-disable-next-line no-console
61
- console.log(`[useModels] Loaded ${fetchedModels.length} ${type} models`);
62
- }
63
-
64
59
  setIsLoading(false);
65
60
  }, [type, config?.initialModelId, defaultModelId]);
66
61
 
@@ -73,11 +68,6 @@ export function useModels(props: UseModelsProps): UseModelsReturn {
73
68
  const model = models.find((m) => m.id === modelId);
74
69
  if (model) {
75
70
  setSelectedModel(model);
76
-
77
- if (typeof __DEV__ !== "undefined" && __DEV__) {
78
- // eslint-disable-next-line no-console
79
- console.log(`[useModels] Selected: ${model.name} (${model.id})`);
80
- }
81
71
  }
82
72
  },
83
73
  [models],
@@ -1,64 +0,0 @@
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 "../../domain/types";
10
- import { buildSingleImageInput } from "../utils/base-builders.util";
11
- import {
12
- buildUpscaleInput,
13
- buildPhotoRestoreInput,
14
- buildFaceSwapInput,
15
- buildRemoveBackgroundInput,
16
- buildRemoveObjectInput,
17
- buildReplaceBackgroundInput,
18
- buildKontextStyleTransferInput,
19
- } from "../utils/image-feature-builders.util";
20
-
21
- const DEFAULT_ANIME_SELFIE_PROMPT = "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.";
22
-
23
- export function buildImageFeatureInput(
24
- feature: ImageFeatureType,
25
- data: ImageFeatureInputData,
26
- ): Record<string, unknown> {
27
- const { imageBase64, targetImageBase64, prompt, options } = data;
28
-
29
- switch (feature) {
30
- case "upscale":
31
- case "hd-touch-up":
32
- return buildUpscaleInput(imageBase64, options);
33
-
34
- case "photo-restore":
35
- return buildPhotoRestoreInput(imageBase64, options);
36
-
37
- case "face-swap":
38
- if (!targetImageBase64) {
39
- throw new Error("Face swap requires target image");
40
- }
41
- return buildFaceSwapInput(imageBase64, targetImageBase64, options);
42
-
43
- case "remove-background":
44
- return buildRemoveBackgroundInput(imageBase64, options);
45
-
46
- case "remove-object":
47
- return buildRemoveObjectInput(imageBase64, { prompt, ...options });
48
-
49
- case "replace-background":
50
- if (!prompt) {
51
- throw new Error("Replace background requires prompt");
52
- }
53
- return buildReplaceBackgroundInput(imageBase64, { prompt, ...options });
54
-
55
- case "anime-selfie":
56
- return buildKontextStyleTransferInput(imageBase64, {
57
- prompt: prompt || (options?.prompt as string) || DEFAULT_ANIME_SELFIE_PROMPT,
58
- guidance_scale: options?.guidance_scale as number | undefined,
59
- });
60
-
61
- default:
62
- return buildSingleImageInput(imageBase64, options);
63
- }
64
- }
@@ -1,52 +0,0 @@
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 "../../domain/types";
10
- import {
11
- buildVideoFromImageInput,
12
- buildTextToVideoInput,
13
- } from "../utils/video-feature-builders.util";
14
-
15
- const DEFAULT_VIDEO_PROMPTS: Partial<Record<VideoFeatureType, string>> = {
16
- "image-to-video": "Animate this image with natural, smooth motion while preserving all details",
17
- "text-to-video": "Generate a high-quality video based on the description, smooth motion",
18
- } as const;
19
-
20
- /**
21
- * Features that require image input
22
- */
23
- const IMAGE_REQUIRED_FEATURES: readonly VideoFeatureType[] = [
24
- "image-to-video",
25
- ] as const;
26
-
27
- function isImageRequiredFeature(feature: VideoFeatureType): boolean {
28
- return IMAGE_REQUIRED_FEATURES.includes(feature);
29
- }
30
-
31
- export function buildVideoFeatureInput(
32
- feature: VideoFeatureType,
33
- data: VideoFeatureInputData,
34
- ): Record<string, unknown> {
35
- const { sourceImageBase64, prompt, options } = data;
36
- const effectivePrompt = prompt || DEFAULT_VIDEO_PROMPTS[feature] || "Generate video";
37
-
38
- if (isImageRequiredFeature(feature)) {
39
- return buildVideoFromImageInput(sourceImageBase64 || "", {
40
- prompt: effectivePrompt,
41
- duration: options?.duration as number | undefined,
42
- resolution: options?.resolution as string | undefined,
43
- });
44
- }
45
-
46
- return buildTextToVideoInput({
47
- prompt: effectivePrompt,
48
- duration: options?.duration as number | undefined,
49
- aspectRatio: options?.aspect_ratio as string | undefined,
50
- resolution: options?.resolution as string | undefined,
51
- });
52
- }
@@ -1,48 +0,0 @@
1
- /**
2
- * FAL Feature Models - Model resolution and input building
3
- */
4
-
5
- import type {
6
- ImageFeatureType,
7
- VideoFeatureType,
8
- ImageFeatureInputData,
9
- VideoFeatureInputData,
10
- } from "../../domain/types";
11
- import {
12
- buildImageFeatureInput as buildImageFeatureInputImpl,
13
- } from "../builders/image-feature-builder";
14
- import {
15
- buildVideoFeatureInput as buildVideoFeatureInputImpl,
16
- } from "../builders/video-feature-builder";
17
-
18
- export function getImageFeatureModel(
19
- imageFeatureModels: Record<string, string>,
20
- feature: ImageFeatureType,
21
- ): string {
22
- const model = imageFeatureModels[feature];
23
- if (!model) throw new Error(`No model for image feature: ${feature}`);
24
- return model;
25
- }
26
-
27
- export function getVideoFeatureModel(
28
- videoFeatureModels: Record<string, string>,
29
- feature: VideoFeatureType,
30
- ): string {
31
- const model = videoFeatureModels[feature];
32
- if (!model) throw new Error(`No model for video feature: ${feature}`);
33
- return model;
34
- }
35
-
36
- export function buildImageFeatureInput(
37
- feature: ImageFeatureType,
38
- data: ImageFeatureInputData,
39
- ): Record<string, unknown> {
40
- return buildImageFeatureInputImpl(feature, data);
41
- }
42
-
43
- export function buildVideoFeatureInput(
44
- feature: VideoFeatureType,
45
- data: VideoFeatureInputData,
46
- ): Record<string, unknown> {
47
- return buildVideoFeatureInputImpl(feature, data);
48
- }
@@ -1,103 +0,0 @@
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
- import { formatImageDataUri } from "./image-helpers.util";
16
-
17
- export function buildUpscaleInput(
18
- base64: string,
19
- options?: UpscaleOptions,
20
- ): Record<string, unknown> {
21
- return buildSingleImageInput(base64, {
22
- scale: options?.scaleFactor ?? 2,
23
- face_enhance: options?.enhanceFaces ?? false,
24
- });
25
- }
26
-
27
- export function buildPhotoRestoreInput(
28
- base64: string,
29
- options?: PhotoRestoreOptions,
30
- ): Record<string, unknown> {
31
- return buildSingleImageInput(base64, {
32
- face_enhance: options?.enhanceFaces ?? true,
33
- });
34
- }
35
-
36
- export function buildFaceSwapInput(
37
- sourceBase64: string,
38
- targetBase64: string,
39
- _options?: FaceSwapOptions,
40
- ): Record<string, unknown> {
41
- return {
42
- base_image_url: formatImageDataUri(sourceBase64),
43
- swap_image_url: formatImageDataUri(targetBase64),
44
- };
45
- }
46
-
47
- export function buildRemoveBackgroundInput(
48
- base64: string,
49
- options?: RemoveBackgroundOptions & {
50
- model?: string;
51
- operating_resolution?: string;
52
- output_format?: string;
53
- refine_foreground?: boolean;
54
- },
55
- ): Record<string, unknown> {
56
- return buildSingleImageInput(base64, {
57
- model: options?.model ?? "General Use (Light)",
58
- operating_resolution: options?.operating_resolution ?? "1024x1024",
59
- output_format: options?.output_format ?? "png",
60
- refine_foreground: options?.refine_foreground ?? true,
61
- });
62
- }
63
-
64
- export function buildRemoveObjectInput(
65
- base64: string,
66
- options?: RemoveObjectOptions,
67
- ): Record<string, unknown> {
68
- return buildSingleImageInput(base64, {
69
- mask_url: options?.mask,
70
- prompt: options?.prompt || "Remove the object and fill with background",
71
- });
72
- }
73
-
74
- export function buildReplaceBackgroundInput(
75
- base64: string,
76
- options: ReplaceBackgroundOptions,
77
- ): Record<string, unknown> {
78
- return buildSingleImageInput(base64, {
79
- prompt: options.prompt,
80
- });
81
- }
82
-
83
- export function buildHDTouchUpInput(
84
- base64: string,
85
- options?: UpscaleOptions,
86
- ): Record<string, unknown> {
87
- return buildUpscaleInput(base64, options);
88
- }
89
-
90
- export interface KontextStyleTransferOptions {
91
- prompt: string;
92
- guidance_scale?: number;
93
- }
94
-
95
- export function buildKontextStyleTransferInput(
96
- base64: string,
97
- options: KontextStyleTransferOptions,
98
- ): Record<string, unknown> {
99
- return buildSingleImageInput(base64, {
100
- prompt: options.prompt,
101
- guidance_scale: options.guidance_scale ?? 3.5,
102
- });
103
- }
@@ -1,58 +0,0 @@
1
- /**
2
- * Video Feature Input Builders
3
- * Builder functions for video features
4
- */
5
-
6
- import type {
7
- ImageToImagePromptConfig,
8
- VideoFromImageOptions,
9
- TextToVideoOptions,
10
- } from "../../domain/types";
11
- import { buildSingleImageInput } from "./base-builders.util";
12
- import { formatImageDataUri } from "./image-helpers.util";
13
-
14
- export function buildImageToImageInput(
15
- base64: string,
16
- promptConfig: ImageToImagePromptConfig,
17
- ): Record<string, unknown> {
18
- return buildSingleImageInput(base64, {
19
- prompt: promptConfig.prompt,
20
- negative_prompt: promptConfig.negativePrompt,
21
- strength: promptConfig.strength ?? 0.85,
22
- num_inference_steps: promptConfig.num_inference_steps ?? 50,
23
- guidance_scale: promptConfig.guidance_scale ?? 7.5,
24
- });
25
- }
26
-
27
- export function buildVideoFromImageInput(
28
- base64: string,
29
- options?: VideoFromImageOptions & {
30
- enable_safety_checker?: boolean;
31
- default_prompt?: string;
32
- },
33
- ): Record<string, unknown> {
34
- return {
35
- prompt: options?.prompt || options?.default_prompt || "Generate natural motion video",
36
- image_url: formatImageDataUri(base64),
37
- enable_safety_checker: options?.enable_safety_checker ?? false,
38
- ...(options?.duration && { duration: options.duration }),
39
- ...(options?.resolution && { resolution: options.resolution }),
40
- };
41
- }
42
-
43
- /**
44
- * Build input for text-to-video generation (no image required)
45
- */
46
- export function buildTextToVideoInput(
47
- options: TextToVideoOptions,
48
- ): Record<string, unknown> {
49
- const { prompt, duration, aspectRatio, resolution } = options;
50
-
51
- return {
52
- prompt,
53
- enable_safety_checker: false,
54
- ...(duration && { duration }),
55
- ...(aspectRatio && { aspect_ratio: aspectRatio }),
56
- ...(resolution && { resolution }),
57
- };
58
- }