@umituz/react-native-ai-fal-provider 2.0.29 → 2.0.30
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/constants/default-models.constants.ts +1 -6
- package/src/exports/infrastructure.ts +3 -1
- package/src/exports/presentation.ts +9 -0
- package/src/infrastructure/services/fal-models.service.ts +88 -1
- package/src/infrastructure/services/fal-provider.ts +24 -4
- package/src/infrastructure/services/index.ts +14 -1
- package/src/infrastructure/services/request-store.ts +53 -6
- package/src/infrastructure/utils/cost-tracker.ts +12 -4
- package/src/infrastructure/utils/date-format.util.ts +64 -0
- package/src/infrastructure/utils/error-categorizer.ts +5 -43
- package/src/infrastructure/utils/error-mapper.ts +10 -69
- package/src/infrastructure/utils/fal-error-handler.util.ts +153 -0
- package/src/infrastructure/utils/fal-generation-state-manager.util.ts +93 -0
- package/src/infrastructure/utils/fal-storage.util.ts +9 -9
- package/src/infrastructure/utils/formatting.util.ts +28 -205
- package/src/infrastructure/utils/index.ts +7 -0
- package/src/infrastructure/utils/input-preprocessor.util.ts +16 -14
- package/src/infrastructure/utils/number-format.util.ts +79 -0
- package/src/infrastructure/utils/string-format.util.ts +73 -0
- package/src/presentation/hooks/use-fal-generation.ts +60 -38
- package/src/presentation/hooks/use-models.ts +11 -27
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* String Formatting Utilities
|
|
3
|
+
* Functions for formatting and manipulating strings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Truncate text with ellipsis
|
|
8
|
+
*/
|
|
9
|
+
export function truncateText(text: string, maxLength: number): string {
|
|
10
|
+
if (text.length <= maxLength) {
|
|
11
|
+
return text;
|
|
12
|
+
}
|
|
13
|
+
return text.slice(0, maxLength - 3) + "...";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Capitalize first letter of string
|
|
18
|
+
*/
|
|
19
|
+
export function capitalize(text: string): string {
|
|
20
|
+
if (!text) return text;
|
|
21
|
+
return text.charAt(0).toUpperCase() + text.slice(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Convert string to title case
|
|
26
|
+
*/
|
|
27
|
+
export function toTitleCase(text: string): string {
|
|
28
|
+
return text
|
|
29
|
+
.toLowerCase()
|
|
30
|
+
.split(" ")
|
|
31
|
+
.map((word) => capitalize(word))
|
|
32
|
+
.join(" ");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Convert string to slug
|
|
37
|
+
*/
|
|
38
|
+
export function toSlug(text: string): string {
|
|
39
|
+
return text
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.trim()
|
|
42
|
+
.replace(/[^\w\s-]/g, "")
|
|
43
|
+
.replace(/[\s_-]+/g, "-")
|
|
44
|
+
.replace(/^-+|-+$/g, "");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Format list of items with conjunction
|
|
49
|
+
*/
|
|
50
|
+
export function formatList(items: readonly string[], conjunction: string = "and"): string {
|
|
51
|
+
if (items.length === 0) return "";
|
|
52
|
+
if (items.length === 1) return items[0] ?? "";
|
|
53
|
+
if (items.length === 2) return items.join(` ${conjunction} `);
|
|
54
|
+
|
|
55
|
+
const allButLast = items.slice(0, -1);
|
|
56
|
+
const last = items[items.length - 1];
|
|
57
|
+
return `${allButLast.join(", ")}, ${conjunction} ${last}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Pluralize word based on count
|
|
62
|
+
*/
|
|
63
|
+
export function pluralize(word: string, count: number): string {
|
|
64
|
+
if (count === 1) return word;
|
|
65
|
+
return `${word}s`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Format count with plural word
|
|
70
|
+
*/
|
|
71
|
+
export function formatCount(word: string, count: number): string {
|
|
72
|
+
return `${count} ${pluralize(word, count)}`;
|
|
73
|
+
}
|
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
import { useState, useCallback, useRef, useEffect } from "react";
|
|
7
7
|
import { falProvider } from "../../infrastructure/services/fal-provider";
|
|
8
8
|
import { mapFalError } from "../../infrastructure/utils/error-mapper";
|
|
9
|
-
import
|
|
9
|
+
import { FalGenerationStateManager } from "../../infrastructure/utils/fal-generation-state-manager.util";
|
|
10
|
+
import type { FalJobInput, FalQueueStatus } from "../../domain/entities/fal.types";
|
|
10
11
|
import type { FalErrorInfo } from "../../domain/entities/error.types";
|
|
12
|
+
import type { JobStatus } from "../../domain/types";
|
|
11
13
|
|
|
12
14
|
export interface UseFalGenerationOptions {
|
|
13
15
|
timeoutMs?: number;
|
|
@@ -28,6 +30,19 @@ export interface UseFalGenerationResult<T> {
|
|
|
28
30
|
reset: () => void;
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
function convertJobStatusToFalQueueStatus(status: JobStatus, currentRequestId: string | null): FalQueueStatus {
|
|
34
|
+
return {
|
|
35
|
+
status: status.status as FalQueueStatus["status"],
|
|
36
|
+
requestId: status.requestId ?? currentRequestId ?? "",
|
|
37
|
+
logs: status.logs?.map((log) => ({
|
|
38
|
+
message: log.message,
|
|
39
|
+
level: log.level,
|
|
40
|
+
timestamp: log.timestamp,
|
|
41
|
+
})),
|
|
42
|
+
queuePosition: status.queuePosition,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
31
46
|
export function useFalGeneration<T = unknown>(
|
|
32
47
|
options?: UseFalGenerationOptions
|
|
33
48
|
): UseFalGenerationResult<T> {
|
|
@@ -36,76 +51,82 @@ export function useFalGeneration<T = unknown>(
|
|
|
36
51
|
const [isLoading, setIsLoading] = useState(false);
|
|
37
52
|
const [isCancelling, setIsCancelling] = useState(false);
|
|
38
53
|
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
|
|
54
|
+
const stateManagerRef = useRef<FalGenerationStateManager<T> | null>(null);
|
|
55
|
+
const optionsRef = useRef(options);
|
|
56
|
+
|
|
57
|
+
// Keep optionsRef updated without causing effect re-runs
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
optionsRef.current = options;
|
|
60
|
+
}, [options]);
|
|
42
61
|
|
|
43
|
-
// Cleanup on unmount
|
|
44
62
|
useEffect(() => {
|
|
45
|
-
|
|
63
|
+
stateManagerRef.current = new FalGenerationStateManager<T>({
|
|
64
|
+
onProgress: (status) => {
|
|
65
|
+
optionsRef.current?.onProgress?.(status);
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
stateManagerRef.current.setIsMounted(true);
|
|
70
|
+
|
|
46
71
|
return () => {
|
|
47
|
-
|
|
72
|
+
stateManagerRef.current?.setIsMounted(false);
|
|
48
73
|
if (falProvider.hasRunningRequest()) {
|
|
49
74
|
falProvider.cancelCurrentRequest();
|
|
50
75
|
}
|
|
51
76
|
};
|
|
52
|
-
}, []);
|
|
77
|
+
}, []); // Empty deps - only run on mount/unmount
|
|
53
78
|
|
|
54
79
|
const generate = useCallback(
|
|
55
80
|
async (modelEndpoint: string, input: FalJobInput): Promise<T | null> => {
|
|
56
|
-
|
|
81
|
+
const stateManager = stateManagerRef.current;
|
|
82
|
+
if (!stateManager || !stateManager.checkMounted()) return null;
|
|
57
83
|
|
|
58
|
-
|
|
84
|
+
stateManager.setLastRequest(modelEndpoint, input);
|
|
59
85
|
setIsLoading(true);
|
|
60
86
|
setError(null);
|
|
61
87
|
setData(null);
|
|
62
|
-
|
|
88
|
+
stateManager.setCurrentRequestId(null);
|
|
63
89
|
setIsCancelling(false);
|
|
64
90
|
|
|
65
91
|
try {
|
|
66
92
|
const result = await falProvider.subscribe<T>(modelEndpoint, input, {
|
|
67
|
-
timeoutMs:
|
|
68
|
-
onQueueUpdate: (status) => {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
status: status.status,
|
|
75
|
-
requestId: status.requestId ?? currentRequestIdRef.current ?? "",
|
|
76
|
-
logs: status.logs?.map((log: FalLogEntry) => ({
|
|
77
|
-
message: log.message,
|
|
78
|
-
level: log.level,
|
|
79
|
-
timestamp: log.timestamp,
|
|
80
|
-
})),
|
|
81
|
-
queuePosition: status.queuePosition,
|
|
82
|
-
});
|
|
93
|
+
timeoutMs: optionsRef.current?.timeoutMs,
|
|
94
|
+
onQueueUpdate: (status: JobStatus) => {
|
|
95
|
+
const falStatus = convertJobStatusToFalQueueStatus(
|
|
96
|
+
status,
|
|
97
|
+
stateManager.getCurrentRequestId()
|
|
98
|
+
);
|
|
99
|
+
stateManager.handleQueueUpdate(falStatus);
|
|
83
100
|
},
|
|
84
101
|
});
|
|
85
102
|
|
|
86
|
-
if (!
|
|
103
|
+
if (!stateManager.checkMounted()) return null;
|
|
87
104
|
setData(result);
|
|
88
105
|
return result;
|
|
89
106
|
} catch (err) {
|
|
90
|
-
if (!
|
|
107
|
+
if (!stateManager.checkMounted()) return null;
|
|
91
108
|
const errorInfo = mapFalError(err);
|
|
92
109
|
setError(errorInfo);
|
|
93
|
-
|
|
110
|
+
optionsRef.current?.onError?.(errorInfo);
|
|
94
111
|
return null;
|
|
95
112
|
} finally {
|
|
96
|
-
if (
|
|
113
|
+
if (stateManager.checkMounted()) {
|
|
97
114
|
setIsLoading(false);
|
|
98
115
|
setIsCancelling(false);
|
|
99
116
|
}
|
|
100
117
|
}
|
|
101
118
|
},
|
|
102
|
-
[
|
|
119
|
+
[] // No deps - we use optionsRef.current inside
|
|
103
120
|
);
|
|
104
121
|
|
|
105
122
|
const retry = useCallback(async (): Promise<T | null> => {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
123
|
+
const stateManager = stateManagerRef.current;
|
|
124
|
+
if (!stateManager) return null;
|
|
125
|
+
|
|
126
|
+
const lastRequest = stateManager.getLastRequest();
|
|
127
|
+
if (!lastRequest) return null;
|
|
128
|
+
|
|
129
|
+
return generate(lastRequest.endpoint, lastRequest.input);
|
|
109
130
|
}, [generate]);
|
|
110
131
|
|
|
111
132
|
const cancel = useCallback(() => {
|
|
@@ -121,16 +142,17 @@ export function useFalGeneration<T = unknown>(
|
|
|
121
142
|
setError(null);
|
|
122
143
|
setIsLoading(false);
|
|
123
144
|
setIsCancelling(false);
|
|
124
|
-
|
|
125
|
-
currentRequestIdRef.current = null;
|
|
145
|
+
stateManagerRef.current?.clearLastRequest();
|
|
126
146
|
}, [cancel]);
|
|
127
147
|
|
|
148
|
+
const requestId = stateManagerRef.current?.getCurrentRequestId() ?? null;
|
|
149
|
+
|
|
128
150
|
return {
|
|
129
151
|
data,
|
|
130
152
|
error,
|
|
131
153
|
isLoading,
|
|
132
154
|
isRetryable: error?.retryable ?? false,
|
|
133
|
-
requestId
|
|
155
|
+
requestId,
|
|
134
156
|
isCancelling,
|
|
135
157
|
generate,
|
|
136
158
|
retry,
|
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
import { useState, useEffect, useCallback, useMemo } from "react";
|
|
13
13
|
import { falModelsService } from "../../infrastructure/services/fal-models.service";
|
|
14
14
|
import type { FalModelConfig } from "../../domain/constants/default-models.constants";
|
|
15
|
-
import { DEFAULT_CREDIT_COSTS, DEFAULT_MODEL_IDS } from "../../domain/constants/default-models.constants";
|
|
16
15
|
import type {
|
|
17
16
|
ModelType,
|
|
18
17
|
ModelSelectionConfig,
|
|
@@ -36,28 +35,16 @@ export function useModels(props: UseModelsProps): UseModelsReturn {
|
|
|
36
35
|
const [isLoading, setIsLoading] = useState(true);
|
|
37
36
|
const [error, setError] = useState<string | null>(null);
|
|
38
37
|
|
|
39
|
-
const defaultCreditCost = config?.defaultCreditCost ?? DEFAULT_CREDIT_COSTS[type];
|
|
40
|
-
const defaultModelId = config?.defaultModelId ?? DEFAULT_MODEL_IDS[type];
|
|
41
|
-
|
|
42
38
|
const loadModels = useCallback(() => {
|
|
43
39
|
setIsLoading(true);
|
|
44
40
|
setError(null);
|
|
45
41
|
|
|
46
|
-
const
|
|
47
|
-
setModels(
|
|
48
|
-
|
|
49
|
-
const targetId = config?.initialModelId ?? defaultModelId;
|
|
50
|
-
const initial =
|
|
51
|
-
fetchedModels.find((m) => m.id === targetId) ||
|
|
52
|
-
fetchedModels.find((m) => m.isDefault) ||
|
|
53
|
-
fetchedModels[0];
|
|
54
|
-
|
|
55
|
-
if (initial) {
|
|
56
|
-
setSelectedModel(initial);
|
|
57
|
-
}
|
|
42
|
+
const selectionData = falModelsService.getModelSelectionData(type, config);
|
|
43
|
+
setModels(selectionData.models);
|
|
44
|
+
setSelectedModel(selectionData.selectedModel);
|
|
58
45
|
|
|
59
46
|
setIsLoading(false);
|
|
60
|
-
}, [type, config
|
|
47
|
+
}, [type, config]);
|
|
61
48
|
|
|
62
49
|
useEffect(() => {
|
|
63
50
|
loadModels();
|
|
@@ -68,24 +55,21 @@ export function useModels(props: UseModelsProps): UseModelsReturn {
|
|
|
68
55
|
const model = models.find((m) => m.id === modelId);
|
|
69
56
|
if (model) {
|
|
70
57
|
setSelectedModel(model);
|
|
71
|
-
} else {
|
|
72
|
-
// eslint-disable-next-line no-console
|
|
73
|
-
console.warn(`Model not found: ${modelId}. Available models:`, models.map(m => m.id));
|
|
74
58
|
}
|
|
75
59
|
},
|
|
76
60
|
[models],
|
|
77
61
|
);
|
|
78
62
|
|
|
79
63
|
const creditCost = useMemo(() => {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}, [selectedModel,
|
|
64
|
+
return falModelsService.getModelCreditCost(
|
|
65
|
+
selectedModel?.id ?? falModelsService.getDefaultModelId(type),
|
|
66
|
+
type
|
|
67
|
+
);
|
|
68
|
+
}, [selectedModel, type]);
|
|
85
69
|
|
|
86
70
|
const modelId = useMemo(() => {
|
|
87
|
-
return selectedModel?.id ??
|
|
88
|
-
}, [selectedModel,
|
|
71
|
+
return selectedModel?.id ?? falModelsService.getDefaultModelId(type);
|
|
72
|
+
}, [selectedModel, type]);
|
|
89
73
|
|
|
90
74
|
return {
|
|
91
75
|
models,
|