@umituz/react-native-ai-generation-content 1.82.4 → 1.82.6
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 +27 -2
- package/src/domains/generation/wizard/infrastructure/utils/creation-persistence.types.ts +4 -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 +6 -3
- package/src/domains/generation/wizard/presentation/hooks/generation-result.utils.ts +6 -1
- package/src/domains/generation/wizard/presentation/hooks/useVideoQueueGeneration.ts +5 -0
- package/src/presentation/hooks/generation/orchestrator.ts +0 -4
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.6",
|
|
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
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
import { useEffect, useRef, useMemo } from "react";
|
|
9
9
|
import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
|
|
10
10
|
import { QUEUE_STATUS, CREATION_STATUS } from "../../../../domain/constants/queue-status.constants";
|
|
11
|
-
import { DEFAULT_POLL_INTERVAL_MS } from "../../../../infrastructure/constants/polling.constants";
|
|
11
|
+
import { DEFAULT_POLL_INTERVAL_MS, DEFAULT_MAX_POLL_TIME_MS } from "../../../../infrastructure/constants/polling.constants";
|
|
12
12
|
import {
|
|
13
13
|
extractResultUrl,
|
|
14
14
|
type GenerationResult,
|
|
@@ -76,6 +76,26 @@ export function useProcessingJobsPoller(
|
|
|
76
76
|
if (pollingRef.current.has(creation.id)) return;
|
|
77
77
|
pollingRef.current.add(creation.id);
|
|
78
78
|
|
|
79
|
+
// Stale detection: if creation is older than max poll time, mark as failed
|
|
80
|
+
const ageMs = Date.now() - creation.createdAt.getTime();
|
|
81
|
+
if (ageMs > DEFAULT_MAX_POLL_TIME_MS) {
|
|
82
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
83
|
+
console.log("[ProcessingJobsPoller] Stale job detected, marking as failed:", creation.id, { ageMs });
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
await repository.update(userId, creation.id, {
|
|
87
|
+
status: CREATION_STATUS.FAILED,
|
|
88
|
+
metadata: { error: "Generation timed out" },
|
|
89
|
+
});
|
|
90
|
+
} catch (e) {
|
|
91
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
92
|
+
console.error("[ProcessingJobsPoller] Failed to mark stale job:", e);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
pollingRef.current.delete(creation.id);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
79
99
|
const provider = providerRegistry.getActiveProvider();
|
|
80
100
|
if (!provider || !provider.isInitialized()) {
|
|
81
101
|
pollingRef.current.delete(creation.id);
|
|
@@ -114,10 +134,15 @@ export function useProcessingJobsPoller(
|
|
|
114
134
|
return;
|
|
115
135
|
}
|
|
116
136
|
|
|
137
|
+
const output: Record<string, string | undefined> = {};
|
|
138
|
+
if (urls.imageUrl) output.imageUrl = urls.imageUrl;
|
|
139
|
+
if (urls.videoUrl) output.videoUrl = urls.videoUrl;
|
|
140
|
+
if (urls.thumbnailUrl) output.thumbnailUrl = urls.thumbnailUrl;
|
|
141
|
+
|
|
117
142
|
await repository.update(userId, creation.id, {
|
|
118
143
|
status: CREATION_STATUS.COMPLETED,
|
|
119
144
|
uri,
|
|
120
|
-
output
|
|
145
|
+
output,
|
|
121
146
|
});
|
|
122
147
|
} else if (status.status === QUEUE_STATUS.FAILED) {
|
|
123
148
|
if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[ProcessingJobsPoller] Failed:", creation.id);
|
|
@@ -16,10 +16,14 @@ 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 {
|
|
22
25
|
readonly uri: string;
|
|
23
26
|
readonly imageUrl?: string;
|
|
24
27
|
readonly videoUrl?: string;
|
|
28
|
+
readonly thumbnailUrl?: string;
|
|
25
29
|
}
|
|
@@ -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
|
|
|
@@ -17,15 +17,17 @@ export async function updateToCompleted(
|
|
|
17
17
|
creationId: string,
|
|
18
18
|
data: CompletedCreationData
|
|
19
19
|
): Promise<void> {
|
|
20
|
-
const output: { imageUrl?: string; videoUrl?: string } = {};
|
|
20
|
+
const output: { imageUrl?: string; videoUrl?: string; thumbnailUrl?: string } = {};
|
|
21
21
|
if (data.imageUrl) output.imageUrl = data.imageUrl;
|
|
22
22
|
if (data.videoUrl) output.videoUrl = data.videoUrl;
|
|
23
|
+
if (data.thumbnailUrl) output.thumbnailUrl = data.thumbnailUrl;
|
|
23
24
|
|
|
24
25
|
await repository.update(userId, creationId, {
|
|
25
26
|
uri: data.uri,
|
|
26
27
|
status: "completed" as const,
|
|
27
28
|
output,
|
|
28
|
-
|
|
29
|
+
completedAt: new Date(),
|
|
30
|
+
} as Partial<import("../../../../creations/domain/entities/Creation").Creation>);
|
|
29
31
|
|
|
30
32
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
31
33
|
console.log("[CreationPersistence] Updated to completed", { creationId });
|
|
@@ -44,7 +46,8 @@ export async function updateToFailed(
|
|
|
44
46
|
await repository.update(userId, creationId, {
|
|
45
47
|
status: "failed" as const,
|
|
46
48
|
metadata: { error },
|
|
47
|
-
|
|
49
|
+
completedAt: new Date(),
|
|
50
|
+
} as Partial<import("../../../../creations/domain/entities/Creation").Creation>);
|
|
48
51
|
|
|
49
52
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
50
53
|
console.log("[CreationPersistence] Updated to failed", { creationId, error });
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Provider-agnostic utilities for extracting generation results
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { extractThumbnailUrl } from "../../../../../infrastructure/utils/url-extractor/thumbnail-extractor";
|
|
7
|
+
|
|
6
8
|
export interface GenerationErrorDetail {
|
|
7
9
|
msg?: string;
|
|
8
10
|
type?: string;
|
|
@@ -22,6 +24,7 @@ export interface GenerationResult {
|
|
|
22
24
|
export interface GenerationUrls {
|
|
23
25
|
imageUrl?: string;
|
|
24
26
|
videoUrl?: string;
|
|
27
|
+
thumbnailUrl?: string;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
/**
|
|
@@ -59,8 +62,10 @@ function checkForErrors(result: GenerationResult): void {
|
|
|
59
62
|
export function extractResultUrl(result: GenerationResult): GenerationUrls {
|
|
60
63
|
checkForErrors(result);
|
|
61
64
|
|
|
65
|
+
const thumbnailUrl = extractThumbnailUrl(result);
|
|
66
|
+
|
|
62
67
|
if (result.video?.url && typeof result.video.url === "string") {
|
|
63
|
-
return { videoUrl: result.video.url };
|
|
68
|
+
return { videoUrl: result.video.url, thumbnailUrl };
|
|
64
69
|
}
|
|
65
70
|
|
|
66
71
|
if (typeof result.output === "string" && result.output.length > 0 && result.output.startsWith("http")) {
|
|
@@ -81,6 +81,7 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
81
81
|
uri,
|
|
82
82
|
imageUrl: urls.imageUrl,
|
|
83
83
|
videoUrl: urls.videoUrl,
|
|
84
|
+
thumbnailUrl: urls.thumbnailUrl,
|
|
84
85
|
});
|
|
85
86
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
86
87
|
console.log("[VideoQueue] ✅ Updated completion status in Firestore");
|
|
@@ -157,6 +158,7 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
157
158
|
const inputData = input as Record<string, unknown>;
|
|
158
159
|
const duration = typeof inputData?.duration === "number" ? inputData.duration : undefined;
|
|
159
160
|
const resolution = typeof inputData?.resolution === "string" ? inputData.resolution : undefined;
|
|
161
|
+
const aspectRatio = typeof inputData?.aspectRatio === "string" ? inputData.aspectRatio : undefined;
|
|
160
162
|
|
|
161
163
|
creationId = await persistence.saveAsProcessing(userId, {
|
|
162
164
|
scenarioId: scenario.id,
|
|
@@ -165,6 +167,9 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
|
|
|
165
167
|
duration,
|
|
166
168
|
resolution,
|
|
167
169
|
creditCost,
|
|
170
|
+
aspectRatio,
|
|
171
|
+
provider: "fal",
|
|
172
|
+
outputType: scenario.outputType,
|
|
168
173
|
});
|
|
169
174
|
creationIdRef.current = creationId;
|
|
170
175
|
} catch (error) {
|
|
@@ -25,10 +25,6 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
25
25
|
): UseGenerationOrchestratorReturn<TInput, TResult> => {
|
|
26
26
|
const { userId, alertMessages, onSuccess, onError, moderation, lifecycle } = config;
|
|
27
27
|
|
|
28
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
29
|
-
console.log("[Orchestrator] Hook initialized:", { userId });
|
|
30
|
-
}
|
|
31
|
-
|
|
32
28
|
const [state, setState] = useState<GenerationState<TResult>>(INITIAL_STATE);
|
|
33
29
|
const isGeneratingRef = useRef(false);
|
|
34
30
|
const isMountedRef = useRef(true);
|