@umituz/react-native-ai-generation-content 1.83.0 → 1.83.1

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.83.0",
3
+ "version": "1.83.1",
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",
@@ -32,18 +32,20 @@ export const CREATION_FIELDS = {
32
32
 
33
33
  // Timestamps
34
34
  CREATED_AT: "createdAt" as const,
35
+ STARTED_AT: "startedAt" as const,
35
36
  UPDATED_AT: "updatedAt" as const,
36
37
  DELETED_AT: "deletedAt" as const,
37
38
  RATED_AT: "ratedAt" as const,
39
+ COMPLETED_AT: "completedAt" as const,
40
+
41
+ // Duration (ms elapsed from startedAt to completedAt)
42
+ DURATION_MS: "durationMs" as const,
38
43
 
39
44
  // User interactions
40
45
  IS_FAVORITE: "isFavorite" as const,
41
46
  IS_SHARED: "isShared" as const,
42
47
  RATING: "rating" as const,
43
48
 
44
- // Completion timestamp
45
- COMPLETED_AT: "completedAt" as const,
46
-
47
49
  // AI provider metadata
48
50
  REQUEST_ID: "requestId" as const,
49
51
  MODEL: "model" as const,
@@ -73,6 +75,8 @@ export const UPDATABLE_FIELDS: ReadonlyArray<CreationFieldName> = [
73
75
  CREATION_FIELDS.MODEL,
74
76
  CREATION_FIELDS.PROMPT,
75
77
  CREATION_FIELDS.COMPLETED_AT,
78
+ CREATION_FIELDS.STARTED_AT,
79
+ CREATION_FIELDS.DURATION_MS,
76
80
  ] as const;
77
81
 
78
82
  /**
@@ -39,11 +39,17 @@ export interface Creation {
39
39
  readonly requestId?: string;
40
40
  readonly model?: string;
41
41
  // Timestamps
42
- readonly completedAt?: Date;
42
+ readonly startedAt?: Date; // When generation was submitted to the queue
43
+ readonly completedAt?: Date; // When generation finished (success or failure)
44
+ readonly durationMs?: number; // Elapsed ms from startedAt to completedAt
43
45
  // Soft delete - if set, the creation is considered deleted
44
46
  readonly deletedAt?: Date;
45
47
  }
46
48
 
49
+ interface FirebaseTimestamp {
50
+ toDate: () => Date;
51
+ }
52
+
47
53
  export interface CreationDocument {
48
54
  readonly uri?: string;
49
55
  readonly prompt?: string;
@@ -61,30 +67,30 @@ export interface CreationDocument {
61
67
  readonly rating?: number;
62
68
  readonly ratedAt?: FirebaseTimestamp | Date | null;
63
69
  readonly createdAt: FirebaseTimestamp | Date;
70
+ readonly startedAt?: FirebaseTimestamp | Date | null;
64
71
  readonly completedAt?: FirebaseTimestamp | Date | null;
65
72
  readonly deletedAt?: FirebaseTimestamp | Date | null;
66
73
  readonly updatedAt?: FirebaseTimestamp | Date | null;
74
+ readonly durationMs?: number;
67
75
  // Background job tracking
68
76
  readonly requestId?: string;
69
77
  readonly model?: string;
70
78
  }
71
79
 
72
- interface FirebaseTimestamp {
73
- toDate: () => Date;
80
+ function toDate(value: FirebaseTimestamp | Date | null | undefined): Date | undefined {
81
+ if (!value) return undefined;
82
+ if (value instanceof Date) return value;
83
+ if (typeof value === "object" && "toDate" in value && typeof (value as FirebaseTimestamp).toDate === "function") {
84
+ return (value as FirebaseTimestamp).toDate();
85
+ }
86
+ return undefined;
74
87
  }
75
88
 
76
89
  export function mapDocumentToCreation(
77
90
  id: string,
78
91
  data: CreationDocument,
79
92
  ): Creation {
80
- let creationDate: Date;
81
- if (data.createdAt instanceof Date) {
82
- creationDate = data.createdAt;
83
- } else if (data.createdAt && typeof data.createdAt === "object" && "toDate" in data.createdAt && typeof data.createdAt.toDate === "function") {
84
- creationDate = data.createdAt.toDate();
85
- } else {
86
- creationDate = new Date();
87
- }
93
+ const creationDate = toDate(data.createdAt) ?? new Date();
88
94
 
89
95
  // Get URI from output or direct fields
90
96
  const uri = data.output?.imageUrl ||
@@ -94,27 +100,6 @@ export function mapDocumentToCreation(
94
100
  data.uri ||
95
101
  "";
96
102
 
97
- let ratedAtDate: Date | undefined;
98
- if (data.ratedAt instanceof Date) {
99
- ratedAtDate = data.ratedAt;
100
- } else if (data.ratedAt && typeof data.ratedAt === "object" && "toDate" in data.ratedAt && typeof data.ratedAt.toDate === "function") {
101
- ratedAtDate = data.ratedAt.toDate();
102
- }
103
-
104
- let deletedAtDate: Date | undefined;
105
- if (data.deletedAt instanceof Date) {
106
- deletedAtDate = data.deletedAt;
107
- } else if (data.deletedAt && typeof data.deletedAt === "object" && "toDate" in data.deletedAt && typeof data.deletedAt.toDate === "function") {
108
- deletedAtDate = data.deletedAt.toDate();
109
- }
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
-
118
103
  return {
119
104
  id,
120
105
  uri,
@@ -126,12 +111,14 @@ export function mapDocumentToCreation(
126
111
  isShared: data.isShared ?? false,
127
112
  isFavorite: data.isFavorite ?? false,
128
113
  rating: data.rating,
129
- ratedAt: ratedAtDate,
114
+ ratedAt: toDate(data.ratedAt),
130
115
  status: data.status as CreationStatus | undefined,
131
116
  output: data.output ?? undefined,
132
117
  requestId: data.requestId,
133
118
  model: data.model,
134
- completedAt: completedAtDate,
135
- deletedAt: deletedAtDate,
119
+ startedAt: toDate(data.startedAt),
120
+ completedAt: toDate(data.completedAt),
121
+ durationMs: data.durationMs,
122
+ deletedAt: toDate(data.deletedAt),
136
123
  };
137
124
  }
@@ -27,6 +27,7 @@ export async function createCreation(
27
27
  ...(creation.prompt !== undefined && { prompt: creation.prompt }),
28
28
  ...(creation.requestId !== undefined && { requestId: creation.requestId }),
29
29
  ...(creation.model !== undefined && { model: creation.model }),
30
+ ...(creation.startedAt !== undefined && { startedAt: creation.startedAt }),
30
31
  };
31
32
 
32
33
  try {
@@ -5,27 +5,11 @@
5
5
  import { updateDoc } from "firebase/firestore";
6
6
  import type { IPathResolver } from "@umituz/react-native-firebase";
7
7
  import type { Creation } from "../../domain/entities/Creation";
8
- import { CREATION_FIELDS, type CreationFieldName } from "../../domain/constants";
8
+ import { UPDATABLE_FIELDS } from "../../domain/constants";
9
9
 
10
10
  declare const __DEV__: boolean;
11
11
 
12
- export const UPDATABLE_FIELDS: ReadonlyArray<CreationFieldName> = [
13
- CREATION_FIELDS.URI,
14
- CREATION_FIELDS.STATUS,
15
- CREATION_FIELDS.OUTPUT,
16
- CREATION_FIELDS.IMAGE_URL,
17
- CREATION_FIELDS.VIDEO_URL,
18
- CREATION_FIELDS.METADATA,
19
- CREATION_FIELDS.IS_SHARED,
20
- CREATION_FIELDS.IS_FAVORITE,
21
- CREATION_FIELDS.RATING,
22
- CREATION_FIELDS.RATED_AT,
23
- CREATION_FIELDS.DELETED_AT,
24
- CREATION_FIELDS.REQUEST_ID,
25
- CREATION_FIELDS.MODEL,
26
- CREATION_FIELDS.PROMPT,
27
- "type" as CreationFieldName,
28
- ] as const;
12
+ export { UPDATABLE_FIELDS };
29
13
 
30
14
  export async function updateCreation(
31
15
  pathResolver: IPathResolver,
@@ -26,4 +26,6 @@ export interface CompletedCreationData {
26
26
  readonly imageUrl?: string;
27
27
  readonly videoUrl?: string;
28
28
  readonly thumbnailUrl?: string;
29
+ /** Unix timestamp (ms) when generation was submitted; used to compute durationMs */
30
+ readonly generationStartedAt?: number;
29
31
  }
@@ -16,9 +16,11 @@ export async function saveAsProcessing(
16
16
  repository: ICreationsRepository,
17
17
  userId: string,
18
18
  data: ProcessingCreationData
19
- ): Promise<string> {
19
+ ): Promise<{ creationId: string; startedAt: Date }> {
20
20
  const creationId = `${data.scenarioId}_${Date.now()}`;
21
21
 
22
+ const startedAt = new Date();
23
+
22
24
  await repository.create(userId, {
23
25
  id: creationId,
24
26
  uri: "",
@@ -26,6 +28,7 @@ export async function saveAsProcessing(
26
28
  prompt: data.prompt,
27
29
  status: "processing" as const,
28
30
  createdAt: new Date(),
31
+ startedAt,
29
32
  isShared: false,
30
33
  isFavorite: false,
31
34
  requestId: data.requestId,
@@ -39,7 +42,6 @@ export async function saveAsProcessing(
39
42
  ...(data.aspectRatio && { aspectRatio: data.aspectRatio }),
40
43
  ...(data.provider && { provider: data.provider }),
41
44
  ...(data.outputType && { outputType: data.outputType }),
42
- startedAt: new Date().toISOString(),
43
45
  },
44
46
  });
45
47
 
@@ -51,5 +53,5 @@ export async function saveAsProcessing(
51
53
  });
52
54
  }
53
55
 
54
- return creationId;
56
+ return { creationId, startedAt };
55
57
  }
@@ -22,11 +22,18 @@ export async function updateToCompleted(
22
22
  if (data.videoUrl) output.videoUrl = data.videoUrl;
23
23
  if (data.thumbnailUrl) output.thumbnailUrl = data.thumbnailUrl;
24
24
 
25
+ const completedAt = new Date();
26
+ const durationMs =
27
+ data.generationStartedAt !== undefined
28
+ ? completedAt.getTime() - data.generationStartedAt
29
+ : undefined;
30
+
25
31
  await repository.update(userId, creationId, {
26
32
  uri: data.uri,
27
33
  status: "completed" as const,
28
34
  output,
29
- completedAt: new Date(),
35
+ completedAt,
36
+ ...(durationMs !== undefined && { durationMs }),
30
37
  } as Partial<import("../../../../creations/domain/entities/Creation").Creation>);
31
38
 
32
39
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -96,6 +96,7 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
96
96
  imageUrl: urls.imageUrl,
97
97
  videoUrl: urls.videoUrl,
98
98
  thumbnailUrl: urls.thumbnailUrl,
99
+ generationStartedAt: pollStartTimeRef.current ?? undefined,
99
100
  });
100
101
  if (typeof __DEV__ !== "undefined" && __DEV__) {
101
102
  console.log("[VideoQueue] ✅ Updated completion status in Firestore");
@@ -205,7 +206,7 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
205
206
  const resolution = typeof inputData?.resolution === "string" ? inputData.resolution : undefined;
206
207
  const aspectRatio = typeof inputData?.aspectRatio === "string" ? inputData.aspectRatio : undefined;
207
208
 
208
- creationId = await persistence.saveAsProcessing(userId, {
209
+ const result = await persistence.saveAsProcessing(userId, {
209
210
  scenarioId: scenario.id,
210
211
  scenarioTitle: scenario.title || scenario.id,
211
212
  prompt,
@@ -216,7 +217,10 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
216
217
  provider: "fal",
217
218
  outputType: scenario.outputType,
218
219
  });
220
+ creationId = result.creationId;
219
221
  creationIdRef.current = creationId;
222
+ // Record the actual DB-level start time for accurate durationMs
223
+ pollStartTimeRef.current = result.startedAt.getTime();
220
224
  } catch (error) {
221
225
  if (typeof __DEV__ !== "undefined" && __DEV__) {
222
226
  console.error("[VideoQueue] Failed to save processing creation:", error);
@@ -264,8 +268,10 @@ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): Us
264
268
  }
265
269
  }
266
270
 
267
- // Start polling: record start time, then poll immediately + on interval
268
- pollStartTimeRef.current = Date.now();
271
+ // Start polling: use DB-level startedAt if available, otherwise fallback to now
272
+ if (pollStartTimeRef.current === null) {
273
+ pollStartTimeRef.current = Date.now();
274
+ }
269
275
  pollingRef.current = setInterval(() => void pollStatusRef.current(), DEFAULT_POLL_INTERVAL_MS);
270
276
  void pollStatusRef.current();
271
277
  },