@umituz/react-native-ai-fal-provider 2.2.0 → 2.2.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-fal-provider",
3
- "version": "2.2.0",
3
+ "version": "2.2.2",
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,19 @@
1
+ /**
2
+ * FAL Model Configuration Type
3
+ */
4
+
5
+ import type { FalModelType } from "../entities/fal.types";
6
+
7
+ export interface FalModelConfig {
8
+ readonly id: string;
9
+ readonly name: string;
10
+ readonly type: FalModelType;
11
+ readonly isDefault?: boolean;
12
+ readonly isActive?: boolean;
13
+ readonly pricing?: {
14
+ readonly freeUserCost: number;
15
+ readonly premiumUserCost: number;
16
+ };
17
+ readonly description?: string;
18
+ readonly order?: number;
19
+ }
@@ -2,13 +2,7 @@
2
2
  * Domain Types Index
3
3
  */
4
4
 
5
- export type {
6
- ModelType,
7
- ModelSelectionConfig,
8
- ModelSelectionState,
9
- ModelSelectionActions,
10
- UseModelsReturn,
11
- } from "./model-selection.types";
5
+ export type { ModelType } from "./model-selection.types";
12
6
 
13
7
  export type {
14
8
  UpscaleOptions,
@@ -1,61 +1,10 @@
1
1
  /**
2
2
  * Model Selection Types
3
- * Types for useModels hook and model selection functionality
4
3
  */
5
4
 
6
- import type { FalModelConfig } from "../constants/default-models.constants";
7
5
  import type { FalModelType } from "../entities/fal.types";
8
6
 
9
- /**
10
- * Public API model types (subset of FalModelType)
11
- * UI components support these core model types
12
- */
13
7
  export type ModelType = Extract<
14
8
  FalModelType,
15
9
  "text-to-image" | "text-to-video" | "image-to-video" | "text-to-voice"
16
10
  >;
17
-
18
- /**
19
- * Configuration for model selection behavior
20
- */
21
- export interface ModelSelectionConfig {
22
- /** Initial model ID to select */
23
- readonly initialModelId?: string;
24
- /** Default credit cost when model has no pricing */
25
- readonly defaultCreditCost?: number;
26
- /** Default model ID when no models loaded */
27
- readonly defaultModelId?: string;
28
- }
29
-
30
- /**
31
- * Model selection state
32
- */
33
- export interface ModelSelectionState {
34
- /** All available models for the type */
35
- readonly models: FalModelConfig[];
36
- /** Currently selected model */
37
- readonly selectedModel: FalModelConfig | null;
38
- /** Credit cost based on selected model */
39
- readonly creditCost: number;
40
- /** Selected model's FAL ID for API calls */
41
- readonly modelId: string;
42
- /** Loading state */
43
- readonly isLoading: boolean;
44
- /** Error message if fetch failed */
45
- readonly error: string | null;
46
- }
47
-
48
- /**
49
- * Model selection actions
50
- */
51
- export interface ModelSelectionActions {
52
- /** Select a model by ID */
53
- readonly selectModel: (modelId: string) => void;
54
- /** Refresh models from source */
55
- readonly refreshModels: () => void;
56
- }
57
-
58
- /**
59
- * Complete return type for useModels hook
60
- */
61
- export type UseModelsReturn = ModelSelectionState & ModelSelectionActions;
@@ -14,13 +14,6 @@ export type {
14
14
  FalSubscribeOptions,
15
15
  } from "../domain/entities/fal.types";
16
16
 
17
- export type {
18
- GenerationCost,
19
- CostTrackerConfig,
20
- CostSummary,
21
- ModelCostInfo,
22
- } from "../domain/entities/cost-tracking.types";
23
-
24
17
  export { FalErrorType } from "../domain/entities/error.types";
25
18
  export type {
26
19
  FalErrorCategory,
@@ -28,22 +21,7 @@ export type {
28
21
  FalErrorMessages,
29
22
  } from "../domain/entities/error.types";
30
23
 
31
- export {
32
- DEFAULT_TEXT_TO_IMAGE_MODELS,
33
- DEFAULT_TEXT_TO_VOICE_MODELS,
34
- DEFAULT_TEXT_TO_VIDEO_MODELS,
35
- DEFAULT_IMAGE_TO_VIDEO_MODELS,
36
- DEFAULT_TEXT_TO_TEXT_MODELS,
37
- DEFAULT_CREDIT_COSTS,
38
- DEFAULT_MODEL_IDS,
39
- getAllDefaultModels,
40
- getDefaultModelsByType,
41
- getDefaultModel,
42
- findModelById,
43
- } from "../domain/constants/default-models.constants";
44
- export type { FalModelConfig } from "../domain/constants/default-models.constants";
45
-
46
- export { FAL_IMAGE_FEATURE_MODELS } from "../domain/constants/feature-models.constants";
24
+ export type { FalModelConfig } from "../domain/types/fal-model-config.types";
47
25
 
48
26
  export type {
49
27
  UpscaleOptions,
@@ -55,11 +33,6 @@ export type {
55
33
  ReplaceBackgroundOptions,
56
34
  VideoFromImageOptions,
57
35
  TextToVideoOptions,
58
- ModelType,
59
- ModelSelectionConfig,
60
- ModelSelectionState,
61
- ModelSelectionActions,
62
- UseModelsReturn,
63
36
  ImageFeatureType,
64
37
  VideoFeatureType,
65
38
  AIProviderConfig,
@@ -50,8 +50,6 @@ export {
50
50
  isString,
51
51
  } from "../infrastructure/utils/validators/string-validator.util";
52
52
 
53
- export { CostTracker } from "../infrastructure/utils/cost-tracker";
54
-
55
53
  export {
56
54
  isFalModelType,
57
55
  isModelType,
@@ -1,30 +1,15 @@
1
1
  /**
2
- * FAL Models Service - Model retrieval and selection logic
2
+ * FAL Models Service - Model utilities
3
3
  */
4
4
 
5
- import type { FalModelType } from "../../domain/entities/fal.types";
6
- import type { ModelType, ModelSelectionConfig } from "../../domain/types/model-selection.types";
7
- import { DEFAULT_CREDIT_COSTS, DEFAULT_MODEL_IDS } from "../../domain/constants/default-models.constants";
8
- import {
9
- type FalModelConfig,
10
- getDefaultModelsByType,
11
- getDefaultModel as getDefaultModelFromConstants,
12
- findModelById as findModelByIdFromConstants,
13
- } from "../../domain/constants/default-models.constants";
5
+ import type { FalModelConfig } from "../../domain/types/fal-model-config.types";
14
6
 
15
7
  export type { FalModelConfig };
16
8
 
17
9
  /**
18
- * Model selection result
10
+ * Sort models by order and name
19
11
  */
20
- export interface ModelSelectionResult {
21
- models: FalModelConfig[];
22
- selectedModel: FalModelConfig | null;
23
- defaultCreditCost: number;
24
- defaultModelId: string;
25
- }
26
-
27
- function sortModels(models: FalModelConfig[]): FalModelConfig[] {
12
+ export function sortModels(models: FalModelConfig[]): FalModelConfig[] {
28
13
  return [...models].sort((a, b) => {
29
14
  if (a.order !== b.order) {
30
15
  return (a.order ?? 0) - (b.order ?? 0);
@@ -33,110 +18,23 @@ function sortModels(models: FalModelConfig[]): FalModelConfig[] {
33
18
  });
34
19
  }
35
20
 
36
- export function getModels(type: FalModelType): FalModelConfig[] {
37
- return sortModels(getDefaultModelsByType(type));
38
- }
39
-
40
- export function getDefaultModel(type: FalModelType): FalModelConfig | undefined {
41
- return getDefaultModelFromConstants(type);
42
- }
43
-
44
- export function findModelById(id: string): FalModelConfig | undefined {
45
- return findModelByIdFromConstants(id);
46
- }
47
-
48
- export function getModelPricing(modelId: string): { freeUserCost: number; premiumUserCost: number } | null {
49
- const model = findModelById(modelId);
50
- return model?.pricing ?? null;
51
- }
52
-
53
- /**
54
- * Get credit cost for a model
55
- * Returns the model's free user cost if available, otherwise returns the default cost for the type
56
- * NOTE: Use ?? instead of || to handle 0 values correctly (free models)
57
- */
58
- export function getModelCreditCost(modelId: string, modelType: FalModelType): number {
59
- const pricing = getModelPricing(modelId);
60
- // CRITICAL: Use !== undefined instead of truthy check
61
- // because freeUserCost can be 0 for free models!
62
- if (pricing && pricing.freeUserCost !== undefined) {
63
- return pricing.freeUserCost;
64
- }
65
- return DEFAULT_CREDIT_COSTS[modelType] ?? 0;
66
- }
67
-
68
21
  /**
69
- * Get default credit cost for a model type
22
+ * Find model by ID
70
23
  */
71
- export function getDefaultCreditCost(modelType: FalModelType): number {
72
- return DEFAULT_CREDIT_COSTS[modelType] ?? 0;
24
+ export function findModelById(id: string, models: FalModelConfig[]): FalModelConfig | undefined {
25
+ return models.find((m) => m.id === id);
73
26
  }
74
27
 
75
28
  /**
76
- * Get default model ID for a model type
29
+ * Get default model from a list
77
30
  */
78
- export function getDefaultModelId(modelType: FalModelType): string {
79
- return DEFAULT_MODEL_IDS[modelType] ?? "";
31
+ export function getDefaultModel(models: FalModelConfig[]): FalModelConfig | undefined {
32
+ if (models.length === 0) return undefined;
33
+ return models.find((m) => m.isDefault) ?? models[0];
80
34
  }
81
35
 
82
- /**
83
- * Select initial model based on configuration
84
- * Returns the model matching initialModelId, or the default model, or the first model
85
- */
86
- export function selectInitialModel(
87
- models: FalModelConfig[],
88
- config: ModelSelectionConfig | undefined,
89
- modelType: ModelType
90
- ): FalModelConfig | null {
91
- if (models.length === 0) {
92
- return null;
93
- }
94
-
95
- const defaultModelId = getDefaultModelId(modelType);
96
- const targetId = config?.initialModelId ?? defaultModelId;
97
-
98
- return (
99
- models.find((m) => m.id === targetId) ??
100
- models.find((m) => m.isDefault) ??
101
- models[0]
102
- );
103
- }
104
-
105
- /**
106
- * Get model selection data for a model type
107
- * Returns models, selected model, and default configuration
108
- */
109
- export function getModelSelectionData(
110
- modelType: ModelType,
111
- config?: ModelSelectionConfig
112
- ): ModelSelectionResult {
113
- // ModelType is now a subset of FalModelType, so this cast is safe
114
- const models = getModels(modelType);
115
- const defaultCreditCost = config?.defaultCreditCost ?? getDefaultCreditCost(modelType);
116
- const defaultModelId = config?.defaultModelId ?? getDefaultModelId(modelType);
117
- const selectedModel = selectInitialModel(models, config, modelType);
118
-
119
- return {
120
- models,
121
- selectedModel,
122
- defaultCreditCost,
123
- defaultModelId,
124
- };
125
- }
126
-
127
- // Singleton service export
128
36
  export const falModelsService = {
129
- getModels,
130
- getDefaultModel,
37
+ sortModels,
131
38
  findById: findModelById,
132
- getModelPricing,
133
- getModelCreditCost,
134
- getDefaultCreditCost,
135
- getDefaultModelId,
136
- selectInitialModel,
137
- getModelSelectionData,
138
- getTextToImageModels: () => getModels("text-to-image"),
139
- getTextToVoiceModels: () => getModels("text-to-voice"),
140
- getTextToVideoModels: () => getModels("text-to-video"),
141
- getImageToVideoModels: () => getModels("image-to-video"),
39
+ getDefaultModel,
142
40
  };
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * FAL Provider - Implements IAIProvider interface
3
- * Orchestrates FAL AI operations with Promise Deduplication
4
3
  */
5
4
 
6
5
  import { fal } from "@fal-ai/client";
@@ -9,10 +8,9 @@ import type {
9
8
  RunOptions, ProviderCapabilities, ImageFeatureType, VideoFeatureType,
10
9
  ImageFeatureInputData, VideoFeatureInputData,
11
10
  } from "../../domain/types";
12
- import type { CostTrackerConfig } from "../../domain/entities/cost-tracking.types";
13
11
  import { DEFAULT_FAL_CONFIG, FAL_CAPABILITIES } from "./fal-provider.constants";
14
12
  import { handleFalSubscription, handleFalRun } from "./fal-provider-subscription";
15
- import { CostTracker, executeWithCostTracking, preprocessInput } from "../utils";
13
+ import { preprocessInput } from "../utils";
16
14
  import {
17
15
  createRequestKey, getExistingRequest, storeRequest,
18
16
  removeRequest, cancelAllRequests, hasActiveRequests,
@@ -26,7 +24,6 @@ export class FalProvider implements IAIProvider {
26
24
 
27
25
  private apiKey: string | null = null;
28
26
  private initialized = false;
29
- private costTracker: CostTracker | null = null;
30
27
 
31
28
  initialize(config: AIProviderConfig): void {
32
29
  this.apiKey = config.apiKey;
@@ -41,22 +38,6 @@ export class FalProvider implements IAIProvider {
41
38
  this.initialized = true;
42
39
  }
43
40
 
44
- enableCostTracking(config?: CostTrackerConfig): void {
45
- this.costTracker = new CostTracker(config);
46
- }
47
-
48
- disableCostTracking(): void {
49
- this.costTracker = null;
50
- }
51
-
52
- isCostTrackingEnabled(): boolean {
53
- return this.costTracker !== null;
54
- }
55
-
56
- getCostTracker(): CostTracker | null {
57
- return this.costTracker;
58
- }
59
-
60
41
  isInitialized(): boolean {
61
42
  return this.initialized;
62
43
  }
@@ -70,19 +51,19 @@ export class FalProvider implements IAIProvider {
70
51
  }
71
52
 
72
53
  getImageFeatureModel(_feature: ImageFeatureType): string {
73
- throw new Error("Feature-specific models are not supported in this provider. Use the main app's feature implementations.");
54
+ throw new Error("Feature-specific models not supported. Use main app's feature implementations.");
74
55
  }
75
56
 
76
57
  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.");
58
+ throw new Error("Feature-specific input building not supported. Use main app's feature implementations.");
78
59
  }
79
60
 
80
61
  getVideoFeatureModel(_feature: VideoFeatureType): string {
81
- throw new Error("Feature-specific models are not supported in this provider. Use the main app's feature implementations.");
62
+ throw new Error("Feature-specific models not supported. Use main app's feature implementations.");
82
63
  }
83
64
 
84
65
  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.");
66
+ throw new Error("Feature-specific input building not supported. Use main app's feature implementations.");
86
67
  }
87
68
 
88
69
  private validateInit(): void {
@@ -97,13 +78,13 @@ export class FalProvider implements IAIProvider {
97
78
 
98
79
  async getJobStatus(model: string, requestId: string): Promise<JobStatus> {
99
80
  this.validateInit();
100
- validateInput(model, {}); // Validate model ID only
81
+ validateInput(model, {});
101
82
  return queueOps.getJobStatus(model, requestId);
102
83
  }
103
84
 
104
85
  async getJobResult<T = unknown>(model: string, requestId: string): Promise<T> {
105
86
  this.validateInit();
106
- validateInput(model, {}); // Validate model ID only
87
+ validateInput(model, {});
107
88
  return queueOps.getJobResult<T>(model, requestId);
108
89
  }
109
90
 
@@ -119,15 +100,10 @@ export class FalProvider implements IAIProvider {
119
100
  const key = createRequestKey(model, processedInput);
120
101
 
121
102
  const existing = getExistingRequest<T>(key);
122
- if (existing) {
123
- return existing.promise;
124
- }
103
+ if (existing) return existing.promise;
125
104
 
126
105
  const abortController = new AbortController();
127
- const tracker = this.costTracker;
128
106
 
129
- // Create promise with resolvers using definite assignment
130
- // This prevents race conditions and ensures type safety
131
107
  let resolvePromise!: (value: T) => void;
132
108
  let rejectPromise!: (error: unknown) => void;
133
109
  const promise = new Promise<T>((resolve, reject) => {
@@ -135,32 +111,17 @@ export class FalProvider implements IAIProvider {
135
111
  rejectPromise = reject;
136
112
  });
137
113
 
138
- // Store promise immediately to enable request deduplication
139
- // Multiple simultaneous calls with same params will get the same promise
140
114
  storeRequest(key, { promise, abortController, createdAt: Date.now() });
141
115
 
142
- // Execute the actual operation and resolve/reject the stored promise
143
- // Note: This promise chain is not awaited - it runs independently
144
- executeWithCostTracking({
145
- tracker,
146
- model,
147
- operation: "subscribe",
148
- execute: () => handleFalSubscription<T>(model, processedInput, options, abortController.signal),
149
- getRequestId: (res) => res.requestId ?? undefined,
150
- })
151
- .then((res) => {
152
- resolvePromise(res.result);
153
- })
154
- .catch((error) => {
155
- rejectPromise(error);
156
- })
116
+ handleFalSubscription<T>(model, processedInput, options, abortController.signal)
117
+ .then((res) => resolvePromise(res.result))
118
+ .catch((error) => rejectPromise(error))
157
119
  .finally(() => {
158
120
  try {
159
121
  removeRequest(key);
160
122
  } catch (cleanupError) {
161
- // Log but don't throw - cleanup errors shouldn't affect the operation result
162
123
  console.error(
163
- `[fal-provider] Error removing request from store: ${key}`,
124
+ `[fal-provider] Error removing request: ${key}`,
164
125
  cleanupError instanceof Error ? cleanupError.message : String(cleanupError)
165
126
  );
166
127
  }
@@ -179,19 +140,13 @@ export class FalProvider implements IAIProvider {
179
140
  throw new Error("Request cancelled by user");
180
141
  }
181
142
 
182
- return executeWithCostTracking({
183
- tracker: this.costTracker,
184
- model,
185
- operation: "run",
186
- execute: () => handleFalRun<T>(model, processedInput, options),
187
- });
143
+ return handleFalRun<T>(model, processedInput, options);
188
144
  }
189
145
 
190
146
  reset(): void {
191
147
  this.cancelCurrentRequest();
192
148
  this.apiKey = null;
193
149
  this.initialized = false;
194
- this.costTracker = null;
195
150
  }
196
151
 
197
152
  cancelCurrentRequest(): void {
@@ -4,7 +4,7 @@
4
4
 
5
5
  export { FalProvider, falProvider } from "./fal-provider";
6
6
  export type { FalProvider as FalProviderType } from "./fal-provider";
7
- export { falModelsService, type FalModelConfig, type ModelSelectionResult } from "./fal-models.service";
7
+ export { falModelsService, type FalModelConfig } from "./fal-models.service";
8
8
  export { NSFWContentError } from "./nsfw-content-error";
9
9
 
10
10
  // Request store exports for advanced use cases
@@ -103,9 +103,5 @@ export {
103
103
  getJobsByStatus,
104
104
  } from "./job-storage";
105
105
 
106
- export { executeWithCostTracking } from "./cost-tracking-executor.util";
107
- export { CostTracker } from "./cost-tracker";
108
- export type { CostSummary, GenerationCost } from "./cost-tracker";
109
-
110
106
  export { FalGenerationStateManager } from "./fal-generation-state-manager.util";
111
107
  export type { GenerationState } from "./fal-generation-state-manager.util";
@@ -1,101 +1,56 @@
1
1
  /**
2
- * useModels Hook
3
- * Manages FAL AI model selection with dynamic credit costs
4
- *
5
- * @example
6
- * const { models, selectedModel, selectModel, creditCost, modelId } = useModels({
7
- * type: "text-to-video",
8
- * config: { defaultCreditCost: 20, defaultModelId: "fal-ai/minimax-video" }
9
- * });
2
+ * useModels Hook - Model selection management
10
3
  */
11
4
 
12
- import { useState, useEffect, useCallback, useMemo } from "react";
13
- import { falModelsService } from "../../infrastructure/services/fal-models.service";
14
- import type { FalModelConfig } from "../../domain/constants/default-models.constants";
15
- import type {
16
- ModelType,
17
- ModelSelectionConfig,
18
- UseModelsReturn,
19
- } from "../../domain/types/model-selection.types";
20
-
21
- export type { UseModelsReturn } from "../../domain/types/model-selection.types";
5
+ import { useState, useCallback, useMemo, useEffect } from "react";
6
+ import { falModelsService, type FalModelConfig } from "../../infrastructure/services/fal-models.service";
22
7
 
23
8
  export interface UseModelsProps {
24
- /** Model type to fetch */
25
- readonly type: ModelType;
26
- /** Optional configuration */
27
- readonly config?: ModelSelectionConfig;
9
+ readonly models: FalModelConfig[];
10
+ readonly initialModelId?: string;
28
11
  }
29
12
 
30
- export function useModels(props: UseModelsProps): UseModelsReturn {
31
- const { type, config } = props;
32
-
33
- const [models, setModels] = useState<FalModelConfig[]>([]);
34
- const [selectedModel, setSelectedModel] = useState<FalModelConfig | null>(null);
35
- const [isLoading, setIsLoading] = useState(true);
36
- const [error, setError] = useState<string | null>(null);
13
+ export interface UseModelsReturn {
14
+ readonly models: FalModelConfig[];
15
+ readonly selectedModel: FalModelConfig | null;
16
+ readonly selectModel: (modelId: string) => void;
17
+ readonly modelId: string;
18
+ }
37
19
 
38
- // Memoize config to prevent unnecessary re-renders when parent re-renders
39
- // Only recreate when actual config values change
40
- const memoizedConfig = useMemo(() => config, [
41
- config?.initialModelId,
42
- config?.defaultCreditCost,
43
- config?.defaultModelId,
44
- ]);
20
+ export function useModels(props: UseModelsProps): UseModelsReturn {
21
+ const { models, initialModelId } = props;
45
22
 
46
- // Unified load function - eliminates duplication between effect and manual reload
47
- const performLoad = useCallback(() => {
48
- setIsLoading(true);
49
- setError(null);
23
+ const sortedModels = useMemo(() => falModelsService.sortModels(models), [models]);
50
24
 
51
- try {
52
- const selectionData = falModelsService.getModelSelectionData(type, memoizedConfig);
53
- setModels(selectionData.models);
54
- setSelectedModel(selectionData.selectedModel);
55
- } catch (err) {
56
- setError(err instanceof Error ? err.message : 'Failed to load models');
57
- } finally {
58
- setIsLoading(false);
25
+ const [selectedModel, setSelectedModel] = useState<FalModelConfig | null>(() => {
26
+ if (initialModelId) {
27
+ const initial = falModelsService.findById(initialModelId, sortedModels);
28
+ if (initial) return initial;
59
29
  }
60
- }, [type, memoizedConfig]);
30
+ return falModelsService.getDefaultModel(sortedModels) ?? null;
31
+ });
61
32
 
62
- // Auto-load on mount and when dependencies change
63
33
  useEffect(() => {
64
- performLoad();
65
- }, [performLoad]);
66
-
67
- // Alias for manual reloads (same function, clearer name for external API)
68
- const loadModels = performLoad;
34
+ if (initialModelId) {
35
+ const model = falModelsService.findById(initialModelId, sortedModels);
36
+ if (model) setSelectedModel(model);
37
+ }
38
+ }, [initialModelId, sortedModels]);
69
39
 
70
40
  const selectModel = useCallback(
71
41
  (modelId: string) => {
72
- const model = models.find((m) => m.id === modelId);
73
- if (model) {
74
- setSelectedModel(model);
75
- }
42
+ const model = falModelsService.findById(modelId, sortedModels);
43
+ if (model) setSelectedModel(model);
76
44
  },
77
- [models],
45
+ [sortedModels]
78
46
  );
79
47
 
80
- const creditCost = useMemo(() => {
81
- return falModelsService.getModelCreditCost(
82
- selectedModel?.id ?? falModelsService.getDefaultModelId(type),
83
- type
84
- );
85
- }, [selectedModel, type]);
86
-
87
- const modelId = useMemo(() => {
88
- return selectedModel?.id ?? falModelsService.getDefaultModelId(type);
89
- }, [selectedModel, type]);
48
+ const modelId = useMemo(() => selectedModel?.id ?? "", [selectedModel]);
90
49
 
91
50
  return {
92
- models,
51
+ models: sortedModels,
93
52
  selectedModel,
94
53
  selectModel,
95
- creditCost,
96
54
  modelId,
97
- isLoading,
98
- error,
99
- refreshModels: loadModels,
100
55
  };
101
56
  }
@@ -1,113 +0,0 @@
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
- import { DEFAULT_TEXT_TO_IMAGE_MODELS } from "./models/text-to-image.models";
8
- import { DEFAULT_TEXT_TO_VOICE_MODELS } from "./models/text-to-voice.models";
9
- import { DEFAULT_TEXT_TO_VIDEO_MODELS } from "./models/text-to-video.models";
10
- import { DEFAULT_IMAGE_TO_VIDEO_MODELS } from "./models/image-to-video.models";
11
- import { DEFAULT_TEXT_TO_TEXT_MODELS } from "./models/text-to-text.models";
12
-
13
- // Export model lists
14
- export { DEFAULT_TEXT_TO_IMAGE_MODELS };
15
- export { DEFAULT_TEXT_TO_VOICE_MODELS };
16
- export { DEFAULT_TEXT_TO_VIDEO_MODELS };
17
- export { DEFAULT_IMAGE_TO_VIDEO_MODELS };
18
- export { DEFAULT_TEXT_TO_TEXT_MODELS };
19
-
20
- export interface FalModelConfig {
21
- readonly id: string;
22
- readonly name: string;
23
- readonly type: FalModelType;
24
- readonly isDefault?: boolean;
25
- readonly isActive?: boolean;
26
- readonly pricing?: {
27
- readonly freeUserCost: number;
28
- readonly premiumUserCost: number;
29
- };
30
- readonly description?: string;
31
- readonly order?: number;
32
- }
33
-
34
- /**
35
- * Default credit costs for each model type
36
- * Supports all FalModelType values
37
- */
38
- export const DEFAULT_CREDIT_COSTS: Record<FalModelType, number> = {
39
- "text-to-image": 2,
40
- "text-to-video": 20,
41
- "image-to-video": 20,
42
- "text-to-voice": 3,
43
- "image-to-image": 2,
44
- "text-to-text": 1,
45
- } as const;
46
-
47
- /**
48
- * Default model IDs for each model type
49
- * Supports all FalModelType values
50
- */
51
- export const DEFAULT_MODEL_IDS: Record<FalModelType, string> = {
52
- "text-to-image": "fal-ai/flux/schnell",
53
- "text-to-video": "fal-ai/minimax-video",
54
- "image-to-video": "fal-ai/kling-video/v1.5/pro/image-to-video",
55
- "text-to-voice": "fal-ai/playai/tts/v3",
56
- "image-to-image": "fal-ai/flux/schnell",
57
- "text-to-text": "fal-ai/flux/schnell",
58
- } as const;
59
-
60
- /**
61
- * Get all default models
62
- */
63
- export function getAllDefaultModels(): FalModelConfig[] {
64
- return [
65
- ...DEFAULT_TEXT_TO_IMAGE_MODELS,
66
- ...DEFAULT_TEXT_TO_VOICE_MODELS,
67
- ...DEFAULT_TEXT_TO_VIDEO_MODELS,
68
- ...DEFAULT_IMAGE_TO_VIDEO_MODELS,
69
- ...DEFAULT_TEXT_TO_TEXT_MODELS,
70
- ];
71
- }
72
-
73
- /**
74
- * Get default models by type
75
- */
76
- export function getDefaultModelsByType(type: FalModelType): FalModelConfig[] {
77
- switch (type) {
78
- case "text-to-image":
79
- return DEFAULT_TEXT_TO_IMAGE_MODELS;
80
- case "text-to-voice":
81
- return DEFAULT_TEXT_TO_VOICE_MODELS;
82
- case "text-to-video":
83
- return DEFAULT_TEXT_TO_VIDEO_MODELS;
84
- case "image-to-video":
85
- return DEFAULT_IMAGE_TO_VIDEO_MODELS;
86
- case "text-to-text":
87
- return DEFAULT_TEXT_TO_TEXT_MODELS;
88
- case "image-to-image":
89
- return [];
90
- default: {
91
- return [];
92
- }
93
- }
94
- }
95
-
96
- /**
97
- * Get default model for a type
98
- * Returns the model marked as default, or the first model, or undefined if no models exist
99
- */
100
- export function getDefaultModel(type: FalModelType): FalModelConfig | undefined {
101
- const models = getDefaultModelsByType(type);
102
- if (models.length === 0) {
103
- return undefined;
104
- }
105
- return models.find((m) => m.isDefault) ?? models[0];
106
- }
107
-
108
- /**
109
- * Find model by ID across all types
110
- */
111
- export function findModelById(id: string): FalModelConfig | undefined {
112
- return getAllDefaultModels().find((m) => m.id === id);
113
- }
@@ -1,21 +0,0 @@
1
- /**
2
- * FAL Feature Models Catalog
3
- * Default model IDs for image processing features
4
- * Video models are provided by the app via config
5
- */
6
-
7
- import type { ImageFeatureType } from "../types";
8
-
9
- /**
10
- * FAL model IDs for IMAGE processing features
11
- */
12
- export const FAL_IMAGE_FEATURE_MODELS: Record<ImageFeatureType, string> = {
13
- "upscale": "fal-ai/clarity-upscaler",
14
- "photo-restore": "fal-ai/aura-sr",
15
- "face-swap": "fal-ai/face-swap",
16
- "anime-selfie": "fal-ai/flux-pro/kontext",
17
- "remove-background": "fal-ai/birefnet",
18
- "remove-object": "fal-ai/fooocus/inpaint",
19
- "hd-touch-up": "fal-ai/clarity-upscaler",
20
- "replace-background": "fal-ai/bria/background/replace",
21
- } as const;
@@ -1,18 +0,0 @@
1
- /**
2
- * Image-to-Video Models
3
- */
4
-
5
- import type { FalModelConfig } from "../default-models.constants";
6
-
7
- export const DEFAULT_IMAGE_TO_VIDEO_MODELS: FalModelConfig[] = [
8
- {
9
- id: "fal-ai/kling-video/v1.5/pro/image-to-video",
10
- name: "Kling I2V",
11
- type: "image-to-video",
12
- isDefault: true,
13
- isActive: true,
14
- pricing: { freeUserCost: 15, premiumUserCost: 8 },
15
- description: "High-quality image to video generation",
16
- order: 1,
17
- },
18
- ];
@@ -1,10 +0,0 @@
1
- /**
2
- * Model Catalog Index
3
- * Exports all model lists
4
- */
5
-
6
- export { DEFAULT_TEXT_TO_IMAGE_MODELS } from "./text-to-image.models";
7
- export { DEFAULT_TEXT_TO_VOICE_MODELS } from "./text-to-voice.models";
8
- export { DEFAULT_TEXT_TO_VIDEO_MODELS } from "./text-to-video.models";
9
- export { DEFAULT_IMAGE_TO_VIDEO_MODELS } from "./image-to-video.models";
10
- export { DEFAULT_TEXT_TO_TEXT_MODELS } from "./text-to-text.models";
@@ -1,18 +0,0 @@
1
- /**
2
- * Text-to-Image Models
3
- */
4
-
5
- import type { FalModelConfig } from "../default-models.constants";
6
-
7
- export const DEFAULT_TEXT_TO_IMAGE_MODELS: FalModelConfig[] = [
8
- {
9
- id: "xai/grok-imagine-image",
10
- name: "Grok Imagine",
11
- type: "text-to-image",
12
- isDefault: true,
13
- isActive: true,
14
- pricing: { freeUserCost: 0.5, premiumUserCost: 0.25 },
15
- description: "X.AI's cost-effective text-to-image generation ($0.02/image)",
16
- order: 1,
17
- },
18
- ];
@@ -1,18 +0,0 @@
1
- /**
2
- * Text-to-Text Models
3
- */
4
-
5
- import type { FalModelConfig } from "../default-models.constants";
6
-
7
- export const DEFAULT_TEXT_TO_TEXT_MODELS: FalModelConfig[] = [
8
- {
9
- id: "fal-ai/llama-3-8b-instruct",
10
- name: "Llama 3 8B Instruct",
11
- type: "text-to-text",
12
- isDefault: true,
13
- isActive: true,
14
- pricing: { freeUserCost: 0.1, premiumUserCost: 0.05 },
15
- description: "Fast and reliable text generation",
16
- order: 1,
17
- },
18
- ];
@@ -1,48 +0,0 @@
1
- /**
2
- * Text-to-Video Models
3
- */
4
-
5
- import type { FalModelConfig } from "../default-models.constants";
6
-
7
- export const DEFAULT_TEXT_TO_VIDEO_MODELS: FalModelConfig[] = [
8
- {
9
- id: "fal-ai/hunyuan-video/1",
10
- name: "Hunyuan",
11
- type: "text-to-video",
12
- isDefault: true,
13
- isActive: true,
14
- pricing: { freeUserCost: 10, premiumUserCost: 5 },
15
- description: "High-quality video generation",
16
- order: 1,
17
- },
18
- {
19
- id: "fal-ai/minimax-video",
20
- name: "MiniMax",
21
- type: "text-to-video",
22
- isDefault: false,
23
- isActive: true,
24
- pricing: { freeUserCost: 15, premiumUserCost: 8 },
25
- description: "Advanced video generation with better dynamics",
26
- order: 2,
27
- },
28
- {
29
- id: "fal-ai/kling-video/v1.5/pro/text-to-video",
30
- name: "Kling 1.5",
31
- type: "text-to-video",
32
- isDefault: false,
33
- isActive: true,
34
- pricing: { freeUserCost: 20, premiumUserCost: 10 },
35
- description: "Professional video generation",
36
- order: 3,
37
- },
38
- {
39
- id: "fal-ai/mochi-v1",
40
- name: "Mochi",
41
- type: "text-to-video",
42
- isDefault: false,
43
- isActive: true,
44
- pricing: { freeUserCost: 8, premiumUserCost: 4 },
45
- description: "Fast video generation",
46
- order: 4,
47
- },
48
- ];
@@ -1,28 +0,0 @@
1
- /**
2
- * Text-to-Voice Models
3
- */
4
-
5
- import type { FalModelConfig } from "../default-models.constants";
6
-
7
- export const DEFAULT_TEXT_TO_VOICE_MODELS: FalModelConfig[] = [
8
- {
9
- id: "fal-ai/playai/tts/v3",
10
- name: "PlayAI TTS v3",
11
- type: "text-to-voice",
12
- isDefault: true,
13
- isActive: true,
14
- pricing: { freeUserCost: 1, premiumUserCost: 0.5 },
15
- description: "High-quality text-to-speech synthesis",
16
- order: 1,
17
- },
18
- {
19
- id: "fal-ai/eleven-labs/tts",
20
- name: "ElevenLabs TTS",
21
- type: "text-to-voice",
22
- isDefault: false,
23
- isActive: true,
24
- pricing: { freeUserCost: 2, premiumUserCost: 1 },
25
- description: "Premium voice synthesis with multiple voice options",
26
- order: 2,
27
- },
28
- ];
@@ -1,39 +0,0 @@
1
- /**
2
- * Cost Tracking Types
3
- * Real-time cost tracking for AI generation operations
4
- */
5
-
6
- export interface GenerationCost {
7
- readonly model: string;
8
- readonly operation: string;
9
- readonly estimatedCost: number;
10
- readonly actualCost: number;
11
- readonly currency: string;
12
- readonly timestamp: number;
13
- readonly requestId?: string;
14
- readonly metadata?: Record<string, unknown>;
15
- }
16
-
17
- export interface CostTrackerConfig {
18
- readonly currency?: string;
19
- readonly trackEstimatedCost?: boolean;
20
- readonly trackActualCost?: boolean;
21
- readonly onCostUpdate?: (cost: GenerationCost) => void;
22
- }
23
-
24
- export interface ModelCostInfo {
25
- readonly model: string;
26
- readonly costPerRequest: number;
27
- readonly costPerToken?: number;
28
- readonly costPerSecond?: number;
29
- readonly currency: string;
30
- }
31
-
32
- export interface CostSummary {
33
- readonly totalCost: number;
34
- readonly totalGenerations: number;
35
- readonly averageCost: number;
36
- readonly currency: string;
37
- readonly modelBreakdown: Record<string, number>;
38
- readonly operationBreakdown: Record<string, number>;
39
- }
@@ -1,179 +0,0 @@
1
- /**
2
- * Cost Tracker
3
- * Tracks and manages real-time cost information for AI generations
4
- */
5
-
6
- import type {
7
- GenerationCost,
8
- CostTrackerConfig,
9
- ModelCostInfo,
10
- } from "../../domain/entities/cost-tracking.types";
11
- import { findModelById } from "../../domain/constants/default-models.constants";
12
- import { filterByProperty, filterByTimeRange } from "./collections";
13
- import { getErrorMessage } from './helpers/error-helpers.util';
14
-
15
- export type { GenerationCost } from "../../domain/entities/cost-tracking.types";
16
-
17
- export interface CostSummary {
18
- totalEstimatedCost: number;
19
- totalActualCost: number;
20
- currency: string;
21
- operationCount: number;
22
- }
23
-
24
- function calculateCostSummary(costs: GenerationCost[], currency: string): CostSummary {
25
- return costs.reduce(
26
- (summary, cost) => ({
27
- totalEstimatedCost: summary.totalEstimatedCost + cost.estimatedCost,
28
- totalActualCost: summary.totalActualCost + cost.actualCost,
29
- currency,
30
- operationCount: summary.operationCount + 1,
31
- }),
32
- { totalEstimatedCost: 0, totalActualCost: 0, currency, operationCount: 0 }
33
- );
34
- }
35
-
36
- export class CostTracker {
37
- private config: Required<CostTrackerConfig>;
38
- private costHistory: GenerationCost[] = [];
39
- private currentOperationCosts: Map<string, number> = new Map();
40
-
41
- constructor(config?: CostTrackerConfig) {
42
- this.config = {
43
- currency: config?.currency ?? "USD",
44
- trackEstimatedCost: config?.trackEstimatedCost ?? true,
45
- trackActualCost: config?.trackActualCost ?? true,
46
- onCostUpdate: config?.onCostUpdate ?? (() => {}),
47
- };
48
- }
49
-
50
- getModelCostInfo(modelId: string): ModelCostInfo {
51
- try {
52
- const model = findModelById(modelId);
53
-
54
- if (model?.pricing) {
55
- return {
56
- model: modelId,
57
- costPerRequest: model.pricing.freeUserCost,
58
- currency: this.config.currency,
59
- };
60
- }
61
- } catch (error) {
62
- // Log error but continue with default cost info
63
- console.warn(
64
- `[cost-tracker] Failed to get model cost info for ${modelId}:`,
65
- getErrorMessage(error)
66
- );
67
- }
68
-
69
- // Return default cost info (0 cost) if model not found or error occurred
70
- return {
71
- model: modelId,
72
- costPerRequest: 0,
73
- currency: this.config.currency,
74
- };
75
- }
76
-
77
- calculateEstimatedCost(modelId: string): number {
78
- const costInfo = this.getModelCostInfo(modelId);
79
- return costInfo.costPerRequest;
80
- }
81
-
82
- startOperation(modelId: string, operation: string): string {
83
- // Generate unique operation ID
84
- let uniqueId: string;
85
- if (typeof crypto !== 'undefined' && crypto.randomUUID) {
86
- uniqueId = crypto.randomUUID();
87
- } else {
88
- // Fallback: Use timestamp with random component and counter
89
- // Format: timestamp-randomCounter-operationHash
90
- const timestamp = Date.now().toString(36);
91
- const random = Math.random().toString(36).substring(2, 11);
92
- const operationHash = operation.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0).toString(36);
93
- uniqueId = `${timestamp}-${random}-${operationHash}`;
94
- }
95
-
96
- const estimatedCost = this.calculateEstimatedCost(modelId);
97
-
98
- this.currentOperationCosts.set(uniqueId, estimatedCost);
99
-
100
- if (this.config.trackEstimatedCost) {
101
- const cost: GenerationCost = {
102
- model: modelId,
103
- operation,
104
- estimatedCost,
105
- actualCost: 0,
106
- currency: this.config.currency,
107
- timestamp: Date.now(),
108
- };
109
-
110
- this.costHistory.push(cost);
111
- this.config.onCostUpdate(cost);
112
- }
113
-
114
- return uniqueId;
115
- }
116
-
117
- completeOperation(
118
- operationId: string,
119
- modelId: string,
120
- operation: string,
121
- requestId?: string,
122
- actualCost?: number,
123
- ): GenerationCost | null {
124
- const estimatedCost = this.currentOperationCosts.get(operationId) ?? 0;
125
- const finalActualCost = actualCost ?? estimatedCost;
126
-
127
- this.currentOperationCosts.delete(operationId);
128
-
129
- const cost: GenerationCost = {
130
- model: modelId,
131
- operation,
132
- estimatedCost,
133
- actualCost: finalActualCost,
134
- currency: this.config.currency,
135
- timestamp: Date.now(),
136
- requestId,
137
- };
138
-
139
- this.costHistory.push(cost);
140
-
141
- if (this.config.trackActualCost) {
142
- this.config.onCostUpdate(cost);
143
- }
144
-
145
- return cost;
146
- }
147
-
148
- /**
149
- * Mark an operation as failed - removes from pending without adding to history
150
- */
151
- failOperation(operationId: string): void {
152
- this.currentOperationCosts.delete(operationId);
153
- }
154
-
155
- getCostSummary(): CostSummary {
156
- return calculateCostSummary(this.costHistory, this.config.currency);
157
- }
158
-
159
- getCostHistory(): readonly GenerationCost[] {
160
- return this.costHistory;
161
- }
162
-
163
- clearHistory(): void {
164
- this.costHistory = [];
165
- this.currentOperationCosts.clear();
166
- }
167
-
168
- getCostsByModel(modelId: string): GenerationCost[] {
169
- return filterByProperty(this.costHistory, "model", modelId);
170
- }
171
-
172
- getCostsByOperation(operation: string): GenerationCost[] {
173
- return filterByProperty(this.costHistory, "operation", operation);
174
- }
175
-
176
- getCostsByTimeRange(startTime: number, endTime: number): GenerationCost[] {
177
- return filterByTimeRange(this.costHistory, "timestamp", startTime, endTime);
178
- }
179
- }
@@ -1,63 +0,0 @@
1
- /**
2
- * Cost Tracking Executor
3
- * Wraps operations with cost tracking logic
4
- */
5
-
6
- import type { CostTracker } from "./cost-tracker";
7
- import { getErrorMessage } from './helpers/error-helpers.util';
8
-
9
- interface ExecuteWithCostTrackingOptions<T> {
10
- tracker: CostTracker | null;
11
- model: string;
12
- operation: string;
13
- execute: () => Promise<T>;
14
- getRequestId?: (result: T) => string | undefined;
15
- }
16
-
17
- /**
18
- * Execute an operation with cost tracking
19
- * Handles start, complete, and fail operations automatically
20
- */
21
- export async function executeWithCostTracking<T>(
22
- options: ExecuteWithCostTrackingOptions<T>
23
- ): Promise<T> {
24
- const { tracker, model, operation, execute, getRequestId } = options;
25
-
26
- if (!tracker) {
27
- return execute();
28
- }
29
-
30
- const operationId = tracker.startOperation(model, operation);
31
-
32
- try {
33
- const result = await execute();
34
-
35
- try {
36
- const requestId = getRequestId?.(result);
37
- tracker.completeOperation(operationId, model, operation, requestId);
38
- } catch (costError) {
39
- // Cost tracking failure shouldn't break the operation
40
- // Log for debugging and audit trail
41
- console.error(
42
- `[cost-tracking] Failed to complete cost tracking for ${operation} on ${model}:`,
43
- getErrorMessage(costError),
44
- { operationId, model, operation }
45
- );
46
- }
47
-
48
- return result;
49
- } catch (error) {
50
- try {
51
- tracker.failOperation(operationId);
52
- } catch (failError) {
53
- // Cost tracking cleanup failure on error path
54
- // Log for debugging and audit trail
55
- console.error(
56
- `[cost-tracking] Failed to mark operation as failed for ${operation} on ${model}:`,
57
- getErrorMessage(failError),
58
- { operationId, model, operation }
59
- );
60
- }
61
- throw error;
62
- }
63
- }