@umituz/react-native-ai-fal-provider 2.1.10 → 2.2.1
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/model-selection.types.ts +1 -47
- package/src/exports/domain.ts +1 -28
- package/src/exports/infrastructure.ts +0 -2
- package/src/index.ts +3 -0
- package/src/infrastructure/services/fal-models.service.ts +13 -115
- package/src/infrastructure/utils/index.ts +0 -4
- package/src/init/createAiProviderInitModule.ts +21 -0
- package/src/init/registerWithWizard.ts +30 -0
- 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 -38
- 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
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Model Selection Types
|
|
3
|
-
*
|
|
3
|
+
* Generic types for model selection - applications provide their own model lists
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { FalModelConfig } from "../constants/default-models.constants";
|
|
@@ -8,54 +8,8 @@ import type { FalModelType } from "../entities/fal.types";
|
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Public API model types (subset of FalModelType)
|
|
11
|
-
* UI components support these core model types
|
|
12
11
|
*/
|
|
13
12
|
export type ModelType = Extract<
|
|
14
13
|
FalModelType,
|
|
15
14
|
"text-to-image" | "text-to-video" | "image-to-video" | "text-to-voice"
|
|
16
15
|
>;
|
|
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,
|
package/src/index.ts
CHANGED
|
@@ -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
|
};
|
|
@@ -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";
|
|
@@ -33,6 +33,20 @@ export interface AiProviderInitModuleConfig {
|
|
|
33
33
|
* @default ["firebase"]
|
|
34
34
|
*/
|
|
35
35
|
dependsOn?: string[];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Optional callback called after provider is initialized
|
|
39
|
+
* Use this to register the provider with wizard flow:
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* onInitialized: () => {
|
|
43
|
+
* const { providerRegistry } = require('@umituz/react-native-ai-generation-content');
|
|
44
|
+
* const { registerWithWizard } = require('@umituz/react-native-ai-fal-provider');
|
|
45
|
+
* registerWithWizard(providerRegistry);
|
|
46
|
+
* }
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
onInitialized?: () => void;
|
|
36
50
|
}
|
|
37
51
|
|
|
38
52
|
/**
|
|
@@ -61,6 +75,7 @@ export function createAiProviderInitModule(
|
|
|
61
75
|
getApiKey,
|
|
62
76
|
critical = false,
|
|
63
77
|
dependsOn = ['firebase'],
|
|
78
|
+
onInitialized,
|
|
64
79
|
} = config;
|
|
65
80
|
|
|
66
81
|
return {
|
|
@@ -75,10 +90,16 @@ export function createAiProviderInitModule(
|
|
|
75
90
|
return Promise.resolve(false);
|
|
76
91
|
}
|
|
77
92
|
|
|
93
|
+
// Initialize FAL provider
|
|
78
94
|
falProvider.initialize({
|
|
79
95
|
apiKey,
|
|
80
96
|
});
|
|
81
97
|
|
|
98
|
+
// Call optional callback after initialization
|
|
99
|
+
if (onInitialized) {
|
|
100
|
+
onInitialized();
|
|
101
|
+
}
|
|
102
|
+
|
|
82
103
|
return Promise.resolve(true);
|
|
83
104
|
} catch (error) {
|
|
84
105
|
return Promise.resolve(false);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard Flow Registration Helper
|
|
3
|
+
* Use this when your app uses GenericWizardFlow from @umituz/react-native-ai-generation-content
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { falProvider } from '../infrastructure/services';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Register FAL provider with the wizard flow provider registry
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* import { providerRegistry } from '@umituz/react-native-ai-generation-content';
|
|
14
|
+
* import { registerWithWizard } from '@umituz/react-native-ai-fal-provider';
|
|
15
|
+
*
|
|
16
|
+
* // After FAL provider is initialized, register it for wizard flow
|
|
17
|
+
* registerWithWizard(providerRegistry);
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export function registerWithWizard(registry: {
|
|
21
|
+
register: (provider: any) => void;
|
|
22
|
+
setActiveProvider: (id: string) => void;
|
|
23
|
+
}): void {
|
|
24
|
+
registry.register(falProvider);
|
|
25
|
+
registry.setActiveProvider('fal');
|
|
26
|
+
|
|
27
|
+
if (typeof __DEV__ !== 'undefined' && __DEV__) {
|
|
28
|
+
console.log('[FAL Provider] Registered with wizard flow');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -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,38 +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: "fal-ai/flux/schnell",
|
|
10
|
-
name: "Flux Schnell",
|
|
11
|
-
type: "text-to-image",
|
|
12
|
-
isDefault: true,
|
|
13
|
-
isActive: true,
|
|
14
|
-
pricing: { freeUserCost: 1, premiumUserCost: 0.5 },
|
|
15
|
-
description: "Fast and efficient text-to-image generation",
|
|
16
|
-
order: 1,
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
id: "fal-ai/flux/dev",
|
|
20
|
-
name: "Flux Dev",
|
|
21
|
-
type: "text-to-image",
|
|
22
|
-
isDefault: false,
|
|
23
|
-
isActive: true,
|
|
24
|
-
pricing: { freeUserCost: 2, premiumUserCost: 1 },
|
|
25
|
-
description: "High-quality text-to-image generation",
|
|
26
|
-
order: 2,
|
|
27
|
-
},
|
|
28
|
-
{
|
|
29
|
-
id: "fal-ai/flux-pro",
|
|
30
|
-
name: "Flux Pro",
|
|
31
|
-
type: "text-to-image",
|
|
32
|
-
isDefault: false,
|
|
33
|
-
isActive: true,
|
|
34
|
-
pricing: { freeUserCost: 3, premiumUserCost: 1.5 },
|
|
35
|
-
description: "Professional-grade text-to-image generation",
|
|
36
|
-
order: 3,
|
|
37
|
-
},
|
|
38
|
-
];
|
|
@@ -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
|
-
}
|