@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.82.4",
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: "xai/grok-imagine-video/text-to-video",
9
- IMAGE_TO_VIDEO: "xai/grok-imagine-video/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: urls,
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);