@umituz/react-native-ai-fal-provider 3.2.31 → 3.2.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-fal-provider",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.32",
|
|
4
4
|
"description": "FAL AI provider for React Native - implements IAIProvider interface for unified AI generation",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Input Preprocessor Utility
|
|
3
3
|
* Detects and uploads base64/local file images to FAL storage before API calls
|
|
4
|
+
*
|
|
5
|
+
* Upload strategy:
|
|
6
|
+
* - Array fields (image_urls): SEQUENTIAL uploads to avoid bandwidth contention
|
|
7
|
+
* - Individual fields (image_url, face_image_url): parallel (typically only 1)
|
|
4
8
|
*/
|
|
5
9
|
|
|
6
10
|
import { uploadToFalStorage, uploadLocalFileToFalStorage } from "./fal-storage.util";
|
|
@@ -17,10 +21,33 @@ function isLocalFileUri(value: unknown): value is string {
|
|
|
17
21
|
);
|
|
18
22
|
}
|
|
19
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Classify a network error into a user-friendly message.
|
|
26
|
+
* Technical details are preserved in Firestore logs/session subcollection.
|
|
27
|
+
*/
|
|
28
|
+
function classifyUploadError(errorMsg: string): string {
|
|
29
|
+
const lower = errorMsg.toLowerCase();
|
|
30
|
+
|
|
31
|
+
if (lower.includes('timed out') || lower.includes('timeout')) {
|
|
32
|
+
return 'Photo upload took too long. Please try again on a stronger connection (WiFi recommended).';
|
|
33
|
+
}
|
|
34
|
+
if (lower.includes('network request failed') || lower.includes('network') || lower.includes('fetch')) {
|
|
35
|
+
return 'Photo upload failed due to network issues. Please check your internet connection and try again.';
|
|
36
|
+
}
|
|
37
|
+
if (lower.includes('econnrefused') || lower.includes('enotfound')) {
|
|
38
|
+
return 'Could not reach the upload server. Please check your internet connection and try again.';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return errorMsg;
|
|
42
|
+
}
|
|
43
|
+
|
|
20
44
|
/**
|
|
21
45
|
* Preprocess input by uploading base64/local file images to FAL storage.
|
|
22
46
|
* Also strips sync_mode to prevent base64 data URI responses.
|
|
23
47
|
* Returns input with HTTPS URLs instead of base64/local URIs.
|
|
48
|
+
*
|
|
49
|
+
* Array fields are uploaded SEQUENTIALLY to avoid bandwidth contention
|
|
50
|
+
* on slow mobile connections (prevents simultaneous upload failures).
|
|
24
51
|
*/
|
|
25
52
|
export async function preprocessInput(
|
|
26
53
|
input: Record<string, unknown>,
|
|
@@ -38,7 +65,7 @@ export async function preprocessInput(
|
|
|
38
65
|
generationLogCollector.warn(sessionId, TAG, `Stripped sync_mode from input`);
|
|
39
66
|
}
|
|
40
67
|
|
|
41
|
-
// Handle individual image URL keys
|
|
68
|
+
// Handle individual image URL keys (parallel — typically only 1 field)
|
|
42
69
|
let individualUploadCount = 0;
|
|
43
70
|
for (const key of IMAGE_URL_FIELDS) {
|
|
44
71
|
const value = result[key];
|
|
@@ -78,76 +105,45 @@ export async function preprocessInput(
|
|
|
78
105
|
generationLogCollector.log(sessionId, TAG, `${individualUploadCount} individual field upload(s) queued`);
|
|
79
106
|
}
|
|
80
107
|
|
|
81
|
-
// Handle image URL arrays
|
|
108
|
+
// Handle image URL arrays — SEQUENTIAL to avoid bandwidth contention
|
|
82
109
|
for (const arrayField of ["image_urls", "input_image_urls", "reference_image_urls"] as const) {
|
|
83
110
|
if (Array.isArray(result[arrayField]) && (result[arrayField] as unknown[]).length > 0) {
|
|
84
111
|
const imageUrls = result[arrayField] as unknown[];
|
|
85
|
-
generationLogCollector.log(sessionId, TAG, `Processing ${arrayField}: ${imageUrls.length} item(s)`);
|
|
112
|
+
generationLogCollector.log(sessionId, TAG, `Processing ${arrayField}: ${imageUrls.length} item(s) (sequential)`);
|
|
86
113
|
|
|
87
|
-
const
|
|
88
|
-
const
|
|
114
|
+
const processedUrls: string[] = [];
|
|
115
|
+
const arrayStartTime = Date.now();
|
|
89
116
|
|
|
90
117
|
for (let i = 0; i < imageUrls.length; i++) {
|
|
91
118
|
const imageUrl = imageUrls[i];
|
|
92
119
|
|
|
93
120
|
if (!imageUrl) {
|
|
94
|
-
|
|
95
|
-
continue;
|
|
121
|
+
throw new Error(`${arrayField}[${i}] is null or undefined`);
|
|
96
122
|
}
|
|
97
123
|
|
|
98
124
|
if (isBase64DataUri(imageUrl)) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
125
|
+
const sizeKB = Math.round(String(imageUrl).length / 1024);
|
|
126
|
+
generationLogCollector.log(sessionId, TAG, `${arrayField}[${i}/${imageUrls.length}]: base64 (${sizeKB}KB) - uploading...`);
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const url = await uploadToFalStorage(imageUrl, sessionId);
|
|
130
|
+
processedUrls.push(url);
|
|
131
|
+
generationLogCollector.log(sessionId, TAG, `${arrayField}[${i}/${imageUrls.length}]: upload OK`);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
const elapsed = Date.now() - arrayStartTime;
|
|
134
|
+
const technicalMsg = getErrorMessage(error);
|
|
135
|
+
generationLogCollector.error(sessionId, TAG, `${arrayField}[${i}] upload FAILED after ${elapsed}ms: ${technicalMsg}`);
|
|
136
|
+
throw new Error(classifyUploadError(technicalMsg));
|
|
137
|
+
}
|
|
109
138
|
} else if (typeof imageUrl === "string") {
|
|
110
|
-
generationLogCollector.log(sessionId, TAG, `${arrayField}[${i}]: already URL - pass through`);
|
|
111
|
-
|
|
139
|
+
generationLogCollector.log(sessionId, TAG, `${arrayField}[${i}/${imageUrls.length}]: already URL - pass through`);
|
|
140
|
+
processedUrls.push(imageUrl);
|
|
112
141
|
} else {
|
|
113
|
-
|
|
142
|
+
throw new Error(`${arrayField}[${i}] has invalid type: ${typeof imageUrl}`);
|
|
114
143
|
}
|
|
115
144
|
}
|
|
116
145
|
|
|
117
|
-
if (errors.length > 0) {
|
|
118
|
-
generationLogCollector.error(sessionId, TAG, `Validation errors in ${arrayField}`, { errors });
|
|
119
|
-
throw new Error(`Image URL validation failed:\n${errors.join('\n')}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (uploadTasks.length === 0) {
|
|
123
|
-
throw new Error(`${arrayField} array must contain at least one valid image URL`);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const arrayStartTime = Date.now();
|
|
127
|
-
const uploadResults = await Promise.allSettled(
|
|
128
|
-
uploadTasks.map((task) => Promise.resolve(task.url))
|
|
129
|
-
);
|
|
130
|
-
|
|
131
|
-
const processedUrls: string[] = [];
|
|
132
|
-
const uploadErrors: string[] = [];
|
|
133
|
-
|
|
134
|
-
uploadResults.forEach((uploadResult, index) => {
|
|
135
|
-
if (uploadResult.status === 'fulfilled') {
|
|
136
|
-
processedUrls.push(uploadResult.value);
|
|
137
|
-
} else {
|
|
138
|
-
uploadErrors.push(
|
|
139
|
-
`Upload ${index} failed: ${getErrorMessage(uploadResult.reason)}`
|
|
140
|
-
);
|
|
141
|
-
}
|
|
142
|
-
});
|
|
143
|
-
|
|
144
146
|
const arrayElapsed = Date.now() - arrayStartTime;
|
|
145
|
-
|
|
146
|
-
if (uploadErrors.length > 0) {
|
|
147
|
-
generationLogCollector.error(sessionId, TAG, `${arrayField} upload FAILED: ${processedUrls.length}/${uploadTasks.length} succeeded in ${arrayElapsed}ms`);
|
|
148
|
-
throw new Error(`Image upload failures:\n${uploadErrors.join('\n')}`);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
147
|
generationLogCollector.log(sessionId, TAG, `${arrayField}: all ${processedUrls.length} upload(s) succeeded in ${arrayElapsed}ms`);
|
|
152
148
|
result[arrayField] = processedUrls;
|
|
153
149
|
}
|
|
@@ -159,16 +155,16 @@ export async function preprocessInput(
|
|
|
159
155
|
const individualUploadResults = await Promise.allSettled(uploadPromises);
|
|
160
156
|
|
|
161
157
|
const failedUploads = individualUploadResults.filter(
|
|
162
|
-
(
|
|
158
|
+
(r) => r.status === 'rejected'
|
|
163
159
|
);
|
|
164
160
|
|
|
165
161
|
if (failedUploads.length > 0) {
|
|
166
162
|
const successCount = individualUploadResults.length - failedUploads.length;
|
|
167
|
-
const errorMessages = failedUploads.map((
|
|
168
|
-
|
|
163
|
+
const errorMessages = failedUploads.map((r) =>
|
|
164
|
+
r.status === 'rejected' ? getErrorMessage(r.reason) : 'Unknown error'
|
|
169
165
|
);
|
|
170
166
|
generationLogCollector.error(sessionId, TAG, `Individual uploads: ${successCount}/${individualUploadResults.length} succeeded`, { errors: errorMessages });
|
|
171
|
-
throw new Error(
|
|
167
|
+
throw new Error(classifyUploadError(errorMessages[0]));
|
|
172
168
|
}
|
|
173
169
|
}
|
|
174
170
|
|