@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 +1 -1
- package/src/domain/types/fal-model-config.types.ts +19 -0
- package/src/domain/types/index.ts +1 -7
- package/src/domain/types/model-selection.types.ts +0 -51
- package/src/exports/domain.ts +1 -28
- package/src/exports/infrastructure.ts +0 -2
- package/src/infrastructure/services/fal-models.service.ts +13 -115
- package/src/infrastructure/services/fal-provider.ts +13 -58
- package/src/infrastructure/services/index.ts +1 -1
- package/src/infrastructure/utils/index.ts +0 -4
- package/src/presentation/hooks/use-models.ts +30 -75
- package/src/domain/constants/default-models.constants.ts +0 -113
- package/src/domain/constants/feature-models.constants.ts +0 -21
- package/src/domain/constants/models/image-to-video.models.ts +0 -18
- package/src/domain/constants/models/index.ts +0 -10
- package/src/domain/constants/models/text-to-image.models.ts +0 -18
- package/src/domain/constants/models/text-to-text.models.ts +0 -18
- package/src/domain/constants/models/text-to-video.models.ts +0 -48
- package/src/domain/constants/models/text-to-voice.models.ts +0 -28
- package/src/domain/entities/cost-tracking.types.ts +0 -39
- package/src/infrastructure/utils/cost-tracker.ts +0 -179
- package/src/infrastructure/utils/cost-tracking-executor.util.ts +0 -63
package/package.json
CHANGED
|
@@ -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;
|
package/src/exports/domain.ts
CHANGED
|
@@ -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,
|
|
@@ -1,30 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* FAL Models Service - Model
|
|
2
|
+
* FAL Models Service - Model utilities
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type {
|
|
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
|
-
*
|
|
10
|
+
* Sort models by order and name
|
|
19
11
|
*/
|
|
20
|
-
export
|
|
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
|
-
*
|
|
22
|
+
* Find model by ID
|
|
70
23
|
*/
|
|
71
|
-
export function
|
|
72
|
-
return
|
|
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
|
|
29
|
+
* Get default model from a list
|
|
77
30
|
*/
|
|
78
|
-
export function
|
|
79
|
-
|
|
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
|
-
|
|
130
|
-
getDefaultModel,
|
|
37
|
+
sortModels,
|
|
131
38
|
findById: findModelById,
|
|
132
|
-
|
|
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 {
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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, {});
|
|
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, {});
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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
|
-
|
|
25
|
-
readonly
|
|
26
|
-
/** Optional configuration */
|
|
27
|
-
readonly config?: ModelSelectionConfig;
|
|
9
|
+
readonly models: FalModelConfig[];
|
|
10
|
+
readonly initialModelId?: string;
|
|
28
11
|
}
|
|
29
12
|
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
39
|
-
|
|
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
|
-
|
|
47
|
-
const performLoad = useCallback(() => {
|
|
48
|
-
setIsLoading(true);
|
|
49
|
-
setError(null);
|
|
23
|
+
const sortedModels = useMemo(() => falModelsService.sortModels(models), [models]);
|
|
50
24
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
30
|
+
return falModelsService.getDefaultModel(sortedModels) ?? null;
|
|
31
|
+
});
|
|
61
32
|
|
|
62
|
-
// Auto-load on mount and when dependencies change
|
|
63
33
|
useEffect(() => {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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 =
|
|
73
|
-
if (model)
|
|
74
|
-
setSelectedModel(model);
|
|
75
|
-
}
|
|
42
|
+
const model = falModelsService.findById(modelId, sortedModels);
|
|
43
|
+
if (model) setSelectedModel(model);
|
|
76
44
|
},
|
|
77
|
-
[
|
|
45
|
+
[sortedModels]
|
|
78
46
|
);
|
|
79
47
|
|
|
80
|
-
const
|
|
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
|
-
}
|