@umituz/react-native-ai-generation-content 1.82.5 → 1.82.7
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/core/constants/model.constants.ts +2 -2
- package/src/domains/creations/domain/constants/creation-fields.constants.ts +4 -0
- package/src/domains/creations/domain/entities/Creation.ts +11 -0
- package/src/domains/creations/presentation/hooks/useProcessingJobsPoller.ts +7 -3
- package/src/domains/generation/wizard/infrastructure/utils/creation-persistence.types.ts +3 -0
- package/src/domains/generation/wizard/infrastructure/utils/creation-save-operations.ts +4 -0
- package/src/domains/generation/wizard/infrastructure/utils/creation-update-operations.ts +4 -2
- package/src/domains/generation/wizard/presentation/hooks/useVideoQueueGeneration.ts +68 -23
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.82.
|
|
3
|
+
"version": "1.82.7",
|
|
4
4
|
"description": "Provider-agnostic AI generation orchestration for React Native with result preview components",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
export const DEFAULT_MODELS = {
|
|
7
7
|
TEXT_TO_IMAGE: "xai/grok-imagine-image",
|
|
8
|
-
TEXT_TO_VIDEO: "
|
|
9
|
-
IMAGE_TO_VIDEO: "
|
|
8
|
+
TEXT_TO_VIDEO: "fal-ai/ltx-video",
|
|
9
|
+
IMAGE_TO_VIDEO: "fal-ai/ltx-video",
|
|
10
10
|
SCENARIO_VIDEO: "fal-ai/ltx-video",
|
|
11
11
|
} as const;
|
|
@@ -41,6 +41,9 @@ export const CREATION_FIELDS = {
|
|
|
41
41
|
IS_SHARED: "isShared" as const,
|
|
42
42
|
RATING: "rating" as const,
|
|
43
43
|
|
|
44
|
+
// Completion timestamp
|
|
45
|
+
COMPLETED_AT: "completedAt" as const,
|
|
46
|
+
|
|
44
47
|
// AI provider metadata
|
|
45
48
|
REQUEST_ID: "requestId" as const,
|
|
46
49
|
MODEL: "model" as const,
|
|
@@ -69,6 +72,7 @@ export const UPDATABLE_FIELDS: ReadonlyArray<CreationFieldName> = [
|
|
|
69
72
|
CREATION_FIELDS.REQUEST_ID,
|
|
70
73
|
CREATION_FIELDS.MODEL,
|
|
71
74
|
CREATION_FIELDS.PROMPT,
|
|
75
|
+
CREATION_FIELDS.COMPLETED_AT,
|
|
72
76
|
] as const;
|
|
73
77
|
|
|
74
78
|
/**
|
|
@@ -38,6 +38,8 @@ export interface Creation {
|
|
|
38
38
|
// Background job tracking
|
|
39
39
|
readonly requestId?: string;
|
|
40
40
|
readonly model?: string;
|
|
41
|
+
// Timestamps
|
|
42
|
+
readonly completedAt?: Date;
|
|
41
43
|
// Soft delete - if set, the creation is considered deleted
|
|
42
44
|
readonly deletedAt?: Date;
|
|
43
45
|
}
|
|
@@ -61,6 +63,7 @@ export interface CreationDocument {
|
|
|
61
63
|
readonly createdAt: FirebaseTimestamp | Date;
|
|
62
64
|
readonly completedAt?: FirebaseTimestamp | Date | null;
|
|
63
65
|
readonly deletedAt?: FirebaseTimestamp | Date | null;
|
|
66
|
+
readonly updatedAt?: FirebaseTimestamp | Date | null;
|
|
64
67
|
// Background job tracking
|
|
65
68
|
readonly requestId?: string;
|
|
66
69
|
readonly model?: string;
|
|
@@ -105,6 +108,13 @@ export function mapDocumentToCreation(
|
|
|
105
108
|
deletedAtDate = data.deletedAt.toDate();
|
|
106
109
|
}
|
|
107
110
|
|
|
111
|
+
let completedAtDate: Date | undefined;
|
|
112
|
+
if (data.completedAt instanceof Date) {
|
|
113
|
+
completedAtDate = data.completedAt;
|
|
114
|
+
} else if (data.completedAt && typeof data.completedAt === "object" && "toDate" in data.completedAt && typeof data.completedAt.toDate === "function") {
|
|
115
|
+
completedAtDate = data.completedAt.toDate();
|
|
116
|
+
}
|
|
117
|
+
|
|
108
118
|
return {
|
|
109
119
|
id,
|
|
110
120
|
uri,
|
|
@@ -121,6 +131,7 @@ export function mapDocumentToCreation(
|
|
|
121
131
|
output: data.output ?? undefined,
|
|
122
132
|
requestId: data.requestId,
|
|
123
133
|
model: data.model,
|
|
134
|
+
completedAt: completedAtDate,
|
|
124
135
|
deletedAt: deletedAtDate,
|
|
125
136
|
};
|
|
126
137
|
}
|
|
@@ -85,7 +85,8 @@ export function useProcessingJobsPoller(
|
|
|
85
85
|
try {
|
|
86
86
|
await repository.update(userId, creation.id, {
|
|
87
87
|
status: CREATION_STATUS.FAILED,
|
|
88
|
-
metadata: { error: "Generation timed out" },
|
|
88
|
+
metadata: { ...creation.metadata, error: "Generation timed out" },
|
|
89
|
+
completedAt: new Date(),
|
|
89
90
|
});
|
|
90
91
|
} catch (e) {
|
|
91
92
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
@@ -129,7 +130,8 @@ export function useProcessingJobsPoller(
|
|
|
129
130
|
}
|
|
130
131
|
await repository.update(userId, creation.id, {
|
|
131
132
|
status: CREATION_STATUS.FAILED,
|
|
132
|
-
metadata: { error: "No valid result URL received" },
|
|
133
|
+
metadata: { ...creation.metadata, error: "No valid result URL received" },
|
|
134
|
+
completedAt: new Date(),
|
|
133
135
|
});
|
|
134
136
|
return;
|
|
135
137
|
}
|
|
@@ -143,6 +145,7 @@ export function useProcessingJobsPoller(
|
|
|
143
145
|
status: CREATION_STATUS.COMPLETED,
|
|
144
146
|
uri,
|
|
145
147
|
output,
|
|
148
|
+
completedAt: new Date(),
|
|
146
149
|
});
|
|
147
150
|
} else if (status.status === QUEUE_STATUS.FAILED) {
|
|
148
151
|
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[ProcessingJobsPoller] Failed:", creation.id);
|
|
@@ -151,7 +154,8 @@ export function useProcessingJobsPoller(
|
|
|
151
154
|
|
|
152
155
|
await repository.update(userId, creation.id, {
|
|
153
156
|
status: CREATION_STATUS.FAILED,
|
|
154
|
-
metadata: { error: "Generation failed" },
|
|
157
|
+
metadata: { ...creation.metadata, error: "Generation failed" },
|
|
158
|
+
completedAt: new Date(),
|
|
155
159
|
});
|
|
156
160
|
}
|
|
157
161
|
} catch (error) {
|
|
@@ -16,6 +16,9 @@ export interface ProcessingCreationData {
|
|
|
16
16
|
readonly duration?: number;
|
|
17
17
|
readonly resolution?: string;
|
|
18
18
|
readonly creditCost?: number;
|
|
19
|
+
readonly aspectRatio?: string;
|
|
20
|
+
readonly provider?: string;
|
|
21
|
+
readonly outputType?: string;
|
|
19
22
|
}
|
|
20
23
|
|
|
21
24
|
export interface CompletedCreationData {
|
|
@@ -36,6 +36,10 @@ export async function saveAsProcessing(
|
|
|
36
36
|
...(data.duration && { duration: data.duration }),
|
|
37
37
|
...(data.resolution && { resolution: data.resolution }),
|
|
38
38
|
...(data.creditCost && { creditCost: data.creditCost }),
|
|
39
|
+
...(data.aspectRatio && { aspectRatio: data.aspectRatio }),
|
|
40
|
+
...(data.provider && { provider: data.provider }),
|
|
41
|
+
...(data.outputType && { outputType: data.outputType }),
|
|
42
|
+
startedAt: new Date().toISOString(),
|
|
39
43
|
},
|
|
40
44
|
});
|
|
41
45
|
|
|
@@ -26,7 +26,8 @@ export async function updateToCompleted(
|
|
|
26
26
|
uri: data.uri,
|
|
27
27
|
status: "completed" as const,
|
|
28
28
|
output,
|
|
29
|
-
|
|
29
|
+
completedAt: new Date(),
|
|
30
|
+
} as Partial<import("../../../../creations/domain/entities/Creation").Creation>);
|
|
30
31
|
|
|
31
32
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
32
33
|
console.log("[CreationPersistence] Updated to completed", { creationId });
|
|
@@ -45,7 +46,8 @@ export async function updateToFailed(
|
|
|
45
46
|
await repository.update(userId, creationId, {
|
|
46
47
|
status: "failed" as const,
|
|
47
48
|
metadata: { error },
|
|
48
|
-
|
|
49
|
+
completedAt: new Date(),
|
|
50
|
+
} as Partial<import("../../../../creations/domain/entities/Creation").Creation>);
|
|
49
51
|
|
|
50
52
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
51
53
|
console.log("[CreationPersistence] Updated to failed", { creationId, error });
|
|
@@ -25,13 +25,16 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
25
25
|
const consecutiveErrorsRef = useRef(0);
|
|
26
26
|
const [isGenerating, setIsGenerating] = useState(false);
|
|
27
27
|
|
|
28
|
+
const clearPolling = useCallback(() => {
|
|
29
|
+
if (pollingRef.current) {
|
|
30
|
+
clearInterval(pollingRef.current);
|
|
31
|
+
pollingRef.current = null;
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
28
35
|
useEffect(() => {
|
|
29
36
|
return () => {
|
|
30
|
-
|
|
31
|
-
clearInterval(pollingRef.current);
|
|
32
|
-
pollingRef.current = null;
|
|
33
|
-
}
|
|
34
|
-
// Reset all refs on unmount
|
|
37
|
+
clearPolling();
|
|
35
38
|
isGeneratingRef.current = false;
|
|
36
39
|
isPollingRef.current = false;
|
|
37
40
|
consecutiveErrorsRef.current = 0;
|
|
@@ -40,9 +43,10 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
40
43
|
modelRef.current = null;
|
|
41
44
|
setIsGenerating(false);
|
|
42
45
|
};
|
|
43
|
-
}, []);
|
|
46
|
+
}, [clearPolling]);
|
|
44
47
|
|
|
45
48
|
const resetRefs = useCallback(() => {
|
|
49
|
+
clearPolling();
|
|
46
50
|
creationIdRef.current = null;
|
|
47
51
|
requestIdRef.current = null;
|
|
48
52
|
modelRef.current = null;
|
|
@@ -50,10 +54,13 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
50
54
|
isPollingRef.current = false;
|
|
51
55
|
consecutiveErrorsRef.current = 0;
|
|
52
56
|
setIsGenerating(false);
|
|
53
|
-
}, []);
|
|
57
|
+
}, [clearPolling]);
|
|
54
58
|
|
|
55
59
|
const handleComplete = useCallback(
|
|
56
60
|
async (urls: GenerationUrls) => {
|
|
61
|
+
// Stop polling immediately on completion
|
|
62
|
+
clearPolling();
|
|
63
|
+
|
|
57
64
|
const creationId = creationIdRef.current;
|
|
58
65
|
const uri = (urls.videoUrl || urls.imageUrl) ?? "";
|
|
59
66
|
|
|
@@ -72,9 +79,12 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
72
79
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
73
80
|
console.error("[VideoQueue] ❌ Invalid completion data:", { creationId, userId, uri });
|
|
74
81
|
}
|
|
82
|
+
resetRefs();
|
|
83
|
+
onError?.("Invalid completion data - no valid URL received");
|
|
75
84
|
return;
|
|
76
85
|
}
|
|
77
86
|
|
|
87
|
+
let persistenceSucceeded = true;
|
|
78
88
|
if (creationId && userId) {
|
|
79
89
|
try {
|
|
80
90
|
await persistence.updateToCompleted(userId, creationId, {
|
|
@@ -87,27 +97,32 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
87
97
|
console.log("[VideoQueue] ✅ Updated completion status in Firestore");
|
|
88
98
|
}
|
|
89
99
|
} catch (error) {
|
|
100
|
+
persistenceSucceeded = false;
|
|
90
101
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
91
102
|
console.error("[VideoQueue] ❌ Failed to update completion status:", error);
|
|
92
103
|
}
|
|
93
104
|
}
|
|
94
105
|
}
|
|
95
106
|
|
|
107
|
+
resetRefs();
|
|
108
|
+
|
|
109
|
+
// Still call onSuccess even if persistence failed - the generation itself succeeded
|
|
110
|
+
// The video/image URL is valid, user should still see the result
|
|
96
111
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
97
|
-
console.log("[VideoQueue] 🎯 Calling onSuccess callback now...");
|
|
112
|
+
console.log("[VideoQueue] 🎯 Calling onSuccess callback now...", { persistenceSucceeded });
|
|
98
113
|
}
|
|
99
|
-
resetRefs();
|
|
100
114
|
onSuccess?.(urls);
|
|
101
115
|
|
|
102
116
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
103
117
|
console.log("[VideoQueue] ✅ onSuccess callback completed");
|
|
104
118
|
}
|
|
105
119
|
},
|
|
106
|
-
[userId, persistence, onSuccess, resetRefs],
|
|
120
|
+
[userId, persistence, onSuccess, onError, resetRefs, clearPolling],
|
|
107
121
|
);
|
|
108
122
|
|
|
109
123
|
const handleError = useCallback(
|
|
110
124
|
async (errorMsg: string) => {
|
|
125
|
+
clearPolling();
|
|
111
126
|
const creationId = creationIdRef.current;
|
|
112
127
|
if (creationId && userId) {
|
|
113
128
|
try {
|
|
@@ -121,7 +136,7 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
121
136
|
resetRefs();
|
|
122
137
|
onError?.(errorMsg);
|
|
123
138
|
},
|
|
124
|
-
[userId, persistence, onError, resetRefs],
|
|
139
|
+
[userId, persistence, onError, resetRefs, clearPolling],
|
|
125
140
|
);
|
|
126
141
|
|
|
127
142
|
const pollStatus = useCallback(async () => {
|
|
@@ -129,15 +144,21 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
129
144
|
const model = modelRef.current;
|
|
130
145
|
if (!requestId || !model) return;
|
|
131
146
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
147
|
+
try {
|
|
148
|
+
await pollQueueStatus({
|
|
149
|
+
requestId,
|
|
150
|
+
model,
|
|
151
|
+
isPollingRef,
|
|
152
|
+
pollingRef,
|
|
153
|
+
consecutiveErrorsRef,
|
|
154
|
+
onComplete: handleComplete,
|
|
155
|
+
onError: handleError,
|
|
156
|
+
});
|
|
157
|
+
} catch (error) {
|
|
158
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
159
|
+
console.error("[VideoQueue] Unexpected poll error:", error);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
141
162
|
}, [handleComplete, handleError]);
|
|
142
163
|
|
|
143
164
|
const startGeneration = useCallback(
|
|
@@ -158,6 +179,7 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
158
179
|
const inputData = input as Record<string, unknown>;
|
|
159
180
|
const duration = typeof inputData?.duration === "number" ? inputData.duration : undefined;
|
|
160
181
|
const resolution = typeof inputData?.resolution === "string" ? inputData.resolution : undefined;
|
|
182
|
+
const aspectRatio = typeof inputData?.aspectRatio === "string" ? inputData.aspectRatio : undefined;
|
|
161
183
|
|
|
162
184
|
creationId = await persistence.saveAsProcessing(userId, {
|
|
163
185
|
scenarioId: scenario.id,
|
|
@@ -166,20 +188,43 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
166
188
|
duration,
|
|
167
189
|
resolution,
|
|
168
190
|
creditCost,
|
|
191
|
+
aspectRatio,
|
|
192
|
+
provider: "fal",
|
|
193
|
+
outputType: scenario.outputType,
|
|
169
194
|
});
|
|
170
195
|
creationIdRef.current = creationId;
|
|
171
196
|
} catch (error) {
|
|
172
197
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
173
198
|
console.error("[VideoQueue] Failed to save processing creation:", error);
|
|
174
199
|
}
|
|
200
|
+
// Continue without creation tracking - generation can still proceed
|
|
201
|
+
// The video will be generated but won't appear in gallery history
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
let queueResult;
|
|
206
|
+
try {
|
|
207
|
+
queueResult = await strategy.submitToQueue(input);
|
|
208
|
+
} catch (error) {
|
|
209
|
+
// Queue submission threw - reset state and report error
|
|
210
|
+
if (creationId && userId) {
|
|
211
|
+
try {
|
|
212
|
+
await persistence.updateToFailed(userId, creationId, error instanceof Error ? error.message : "Queue submission failed");
|
|
213
|
+
} catch { /* best effort */ }
|
|
175
214
|
}
|
|
215
|
+
isGeneratingRef.current = false;
|
|
216
|
+
setIsGenerating(false);
|
|
217
|
+
onError?.(error instanceof Error ? error.message : "Queue submission failed");
|
|
218
|
+
return;
|
|
176
219
|
}
|
|
177
220
|
|
|
178
|
-
const queueResult = await strategy.submitToQueue(input);
|
|
179
221
|
if (!queueResult.success || !queueResult.requestId || !queueResult.model) {
|
|
180
222
|
if (creationId && userId) {
|
|
181
|
-
|
|
223
|
+
try {
|
|
224
|
+
await persistence.updateToFailed(userId, creationId, queueResult.error || "Queue submission failed");
|
|
225
|
+
} catch { /* best effort */ }
|
|
182
226
|
}
|
|
227
|
+
isGeneratingRef.current = false;
|
|
183
228
|
setIsGenerating(false);
|
|
184
229
|
onError?.(queueResult.error || "Queue submission failed");
|
|
185
230
|
return;
|
|
@@ -201,7 +246,7 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
201
246
|
pollingRef.current = setInterval(() => void pollStatus(), DEFAULT_POLL_INTERVAL_MS);
|
|
202
247
|
void pollStatus();
|
|
203
248
|
},
|
|
204
|
-
[userId, scenario, persistence, strategy, pollStatus, onError],
|
|
249
|
+
[userId, scenario, persistence, strategy, creditCost, pollStatus, onError],
|
|
205
250
|
);
|
|
206
251
|
|
|
207
252
|
return { isGenerating, startGeneration };
|