@umituz/react-native-ai-generation-content 1.17.110 → 1.17.113

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.17.110",
3
+ "version": "1.17.113",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -12,6 +12,11 @@ export class CreationsWriter {
12
12
  constructor(private readonly pathResolver: FirestorePathResolver) { }
13
13
 
14
14
  async create(userId: string, creation: Creation): Promise<void> {
15
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
16
+ // eslint-disable-next-line no-console
17
+ console.log("[CreationsWriter] create() start", { userId, creationId: creation.id });
18
+ }
19
+
15
20
  const docRef = this.pathResolver.getDocRef(userId, creation.id);
16
21
  if (!docRef) throw new Error("Firestore not initialized");
17
22
 
@@ -23,9 +28,23 @@ export class CreationsWriter {
23
28
  metadata: creation.metadata || {},
24
29
  isShared: creation.isShared || false,
25
30
  isFavorite: creation.isFavorite || false,
31
+ status: creation.status,
32
+ output: creation.output,
26
33
  };
27
34
 
28
- await setDoc(docRef, data);
35
+ try {
36
+ await setDoc(docRef, data);
37
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
38
+ // eslint-disable-next-line no-console
39
+ console.log("[CreationsWriter] create() success", { creationId: creation.id });
40
+ }
41
+ } catch (error) {
42
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
43
+ // eslint-disable-next-line no-console
44
+ console.error("[CreationsWriter] create() error", error);
45
+ }
46
+ throw error;
47
+ }
29
48
  }
30
49
 
31
50
  async update(
@@ -59,6 +78,12 @@ export class CreationsWriter {
59
78
  if (updates.prompt !== undefined) {
60
79
  updateData.prompt = updates.prompt;
61
80
  }
81
+ if (updates.status !== undefined) {
82
+ updateData.status = updates.status;
83
+ }
84
+ if (updates.output !== undefined) {
85
+ updateData.output = updates.output;
86
+ }
62
87
 
63
88
  await updateDoc(docRef, updateData);
64
89
  return true;
@@ -40,7 +40,7 @@ function isVideoUrl(url?: string | null): boolean {
40
40
 
41
41
  export function CreationVideoPreview({
42
42
  thumbnailUrl,
43
- videoUrl,
43
+ videoUrl: _videoUrl,
44
44
  status = "completed",
45
45
  aspectRatio = 16 / 9,
46
46
  height,
@@ -27,12 +27,22 @@ export interface CreationData {
27
27
  metadata?: Record<string, unknown>;
28
28
  }
29
29
 
30
+ export interface GenerationStartData {
31
+ creationId: string;
32
+ type: "text-to-video";
33
+ prompt: string;
34
+ metadata?: Record<string, unknown>;
35
+ }
36
+
30
37
  export interface TextToVideoCallbacks {
31
38
  onCreditCheck?: (cost: number) => Promise<boolean>;
32
39
  onCreditDeduct?: (cost: number) => Promise<void>;
33
40
  onAuthCheck?: () => boolean;
34
41
  onModeration?: (prompt: string) => Promise<VideoModerationResult>;
35
- onCreationSave?: (data: CreationData) => Promise<void>;
42
+ /** Called when generation starts - save a "processing" creation */
43
+ onGenerationStart?: (data: GenerationStartData) => Promise<void>;
44
+ /** Called when generation completes - update creation to "completed" */
45
+ onCreationSave?: (data: CreationData & { creationId: string }) => Promise<void>;
36
46
  onGenerate?: (result: TextToVideoResult) => void;
37
47
  onError?: (error: string) => void;
38
48
  onProgress?: (progress: number) => void;
@@ -35,6 +35,7 @@ export type {
35
35
  VideoModerationResult,
36
36
  ProjectData,
37
37
  CreationData,
38
+ GenerationStartData,
38
39
  TextToVideoCallbacks,
39
40
  TextToVideoTranslations,
40
41
  } from "./callback.types";
@@ -27,6 +27,7 @@ export type {
27
27
  VideoModerationResult,
28
28
  ProjectData,
29
29
  CreationData,
30
+ GenerationStartData,
30
31
  GenerationTabsProps,
31
32
  FrameSelectorProps,
32
33
  OptionsPanelProps,
@@ -49,13 +49,26 @@ export async function executeTextToVideo(
49
49
  request: TextToVideoRequest,
50
50
  options: ExecuteTextToVideoOptions,
51
51
  ): Promise<TextToVideoResult> {
52
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
53
+ // eslint-disable-next-line no-console
54
+ console.log("[TextToVideoExecutor] executeTextToVideo() called");
55
+ }
56
+
52
57
  const provider = providerRegistry.getActiveProvider();
53
58
 
54
59
  if (!provider) {
60
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
61
+ // eslint-disable-next-line no-console
62
+ console.error("[TextToVideoExecutor] No AI provider configured");
63
+ }
55
64
  return { success: false, error: "No AI provider configured" };
56
65
  }
57
66
 
58
67
  if (!provider.isInitialized()) {
68
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
69
+ // eslint-disable-next-line no-console
70
+ console.error("[TextToVideoExecutor] AI provider not initialized");
71
+ }
59
72
  return { success: false, error: "AI provider not initialized" };
60
73
  }
61
74
 
@@ -65,27 +78,44 @@ export async function executeTextToVideo(
65
78
 
66
79
  const { model, buildInput, extractResult, onProgress } = options;
67
80
 
68
- if (__DEV__) {
81
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
69
82
  // eslint-disable-next-line no-console
70
- console.log(`[TextToVideo] Provider: ${provider.providerId}, Model: ${model}`);
83
+ console.log(`[TextToVideoExecutor] Provider: ${provider.providerId}, Model: ${model}`);
71
84
  }
72
85
 
73
86
  try {
74
87
  onProgress?.(5);
88
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
89
+ // eslint-disable-next-line no-console
90
+ console.log("[TextToVideoExecutor] Starting provider.run()...");
91
+ }
75
92
 
76
93
  const input = buildInput(request.prompt, request.options);
77
94
 
78
95
  const result = await provider.run(model, input, {
79
96
  onProgress: (progress) => {
97
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
98
+ // eslint-disable-next-line no-console
99
+ console.log("[TextToVideoExecutor] Progress:", progress);
100
+ }
80
101
  onProgress?.(progress);
81
102
  },
82
103
  });
83
104
 
105
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
106
+ // eslint-disable-next-line no-console
107
+ console.log("[TextToVideoExecutor] provider.run() completed", result);
108
+ }
109
+
84
110
  const extractor = extractResult || defaultExtractResult;
85
111
  const extracted = extractor(result);
86
112
  onProgress?.(100);
87
113
 
88
114
  if (!extracted?.videoUrl) {
115
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
116
+ // eslint-disable-next-line no-console
117
+ console.error("[TextToVideoExecutor] No video URL in response");
118
+ }
89
119
  return { success: false, error: "No video in response" };
90
120
  }
91
121
 
@@ -96,9 +126,9 @@ export async function executeTextToVideo(
96
126
  };
97
127
  } catch (error) {
98
128
  const message = error instanceof Error ? error.message : String(error);
99
- if (__DEV__) {
129
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
100
130
  // eslint-disable-next-line no-console
101
- console.error("[TextToVideo] Error:", message);
131
+ console.error("[TextToVideoExecutor] Error:", message);
102
132
  }
103
133
  return { success: false, error: message };
104
134
  }
@@ -124,6 +124,9 @@ export function useTextToVideoFeature(
124
124
 
125
125
  const executeGeneration = useCallback(
126
126
  async (prompt: string, options?: TextToVideoOptions): Promise<TextToVideoResult> => {
127
+ // Generate unique creation ID for tracking
128
+ const creationId = `text-to-video_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
129
+
127
130
  setState((prev) => ({
128
131
  ...prev,
129
132
  isProcessing: true,
@@ -133,59 +136,95 @@ export function useTextToVideoFeature(
133
136
 
134
137
  if (typeof __DEV__ !== "undefined" && __DEV__) {
135
138
  // eslint-disable-next-line no-console
136
- console.log("[TextToVideoFeature] Starting generation with prompt:", prompt);
139
+ console.log("[TextToVideoFeature] Starting generation with prompt:", prompt, "creationId:", creationId);
137
140
  }
138
141
 
139
- const result = await executeTextToVideo(
140
- { prompt, userId, options },
141
- {
142
- model: config.model,
143
- buildInput,
144
- extractResult,
145
- onProgress: (progress) => {
146
- setState((prev) => ({ ...prev, progress }));
147
- callbacks.onProgress?.(progress);
148
- },
149
- },
150
- );
142
+ // Create "processing" creation at start (fire-and-forget to not block generation)
143
+ if (callbacks.onGenerationStart) {
144
+ callbacks.onGenerationStart({
145
+ creationId,
146
+ type: "text-to-video",
147
+ prompt,
148
+ metadata: options as Record<string, unknown> | undefined,
149
+ }).catch((err) => {
150
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
151
+ // eslint-disable-next-line no-console
152
+ console.warn("[TextToVideoFeature] onGenerationStart failed:", err);
153
+ }
154
+ });
155
+ }
151
156
 
152
- if (result.success && result.videoUrl) {
153
- setState((prev) => ({
154
- ...prev,
155
- videoUrl: result.videoUrl ?? null,
156
- thumbnailUrl: result.thumbnailUrl ?? null,
157
- isProcessing: false,
158
- progress: 100,
159
- }));
157
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
158
+ // eslint-disable-next-line no-console
159
+ console.log("[TextToVideoFeature] Starting executeTextToVideo...");
160
+ }
160
161
 
161
- // Deduct credits after successful generation
162
- if (callbacks.onCreditDeduct) {
163
- await callbacks.onCreditDeduct(config.creditCost);
162
+ try {
163
+ const result = await executeTextToVideo(
164
+ { prompt, userId, options },
165
+ {
166
+ model: config.model,
167
+ buildInput,
168
+ extractResult,
169
+ onProgress: (progress) => {
170
+ setState((prev) => ({ ...prev, progress }));
171
+ callbacks.onProgress?.(progress);
172
+ },
173
+ },
174
+ );
175
+
176
+ if (result.success && result.videoUrl) {
177
+ setState((prev) => ({
178
+ ...prev,
179
+ videoUrl: result.videoUrl ?? null,
180
+ thumbnailUrl: result.thumbnailUrl ?? null,
181
+ isProcessing: false,
182
+ progress: 100,
183
+ }));
184
+
185
+ // Deduct credits after successful generation
186
+ if (callbacks.onCreditDeduct) {
187
+ await callbacks.onCreditDeduct(config.creditCost);
188
+ }
189
+
190
+ // Update creation to completed after successful generation
191
+ if (callbacks.onCreationSave) {
192
+ await callbacks.onCreationSave({
193
+ creationId,
194
+ type: "text-to-video",
195
+ videoUrl: result.videoUrl,
196
+ thumbnailUrl: result.thumbnailUrl,
197
+ prompt,
198
+ metadata: options as Record<string, unknown> | undefined,
199
+ });
200
+ }
201
+
202
+ callbacks.onGenerate?.(result);
203
+ } else {
204
+ const error = result.error || "Generation failed";
205
+ setState((prev) => ({
206
+ ...prev,
207
+ isProcessing: false,
208
+ error,
209
+ }));
210
+ callbacks.onError?.(error);
164
211
  }
165
212
 
166
- // Save creation after successful generation
167
- if (callbacks.onCreationSave) {
168
- await callbacks.onCreationSave({
169
- type: "text-to-video",
170
- videoUrl: result.videoUrl,
171
- thumbnailUrl: result.thumbnailUrl,
172
- prompt,
173
- metadata: options as Record<string, unknown> | undefined,
174
- });
213
+ return result;
214
+ } catch (err) {
215
+ const errorMessage = err instanceof Error ? err.message : String(err);
216
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
217
+ // eslint-disable-next-line no-console
218
+ console.error("[TextToVideoFeature] Generation error:", errorMessage);
175
219
  }
176
-
177
- callbacks.onGenerate?.(result);
178
- } else {
179
- const error = result.error || "Generation failed";
180
220
  setState((prev) => ({
181
221
  ...prev,
182
222
  isProcessing: false,
183
- error,
223
+ error: errorMessage,
184
224
  }));
185
- callbacks.onError?.(error);
225
+ callbacks.onError?.(errorMessage);
226
+ return { success: false, error: errorMessage };
186
227
  }
187
-
188
- return result;
189
228
  },
190
229
  [userId, config.model, buildInput, extractResult, callbacks],
191
230
  );
@@ -37,6 +37,7 @@ export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
37
37
  hideGenerateButton,
38
38
  progress,
39
39
  progressIcon,
40
+ isProgressModalVisible,
40
41
  onCloseProgressModal,
41
42
  generateButtonProps,
42
43
  showAdvanced,
@@ -159,12 +160,13 @@ export const AIGenerationForm: React.FC<AIGenerationFormProps> = ({
159
160
 
160
161
  {/* MANDATORY: Progress Modal shows automatically when isGenerating */}
161
162
  <GenerationProgressModal
162
- visible={isGenerating}
163
+ visible={isProgressModalVisible ?? isGenerating}
163
164
  progress={progress ?? 0}
164
165
  icon={progressIcon || "sparkles-outline"}
165
166
  title={translations.progressTitle || translations.generatingButton}
166
167
  message={translations.progressMessage || translations.progressHint}
167
168
  onClose={onCloseProgressModal}
169
+ backgroundHint={onCloseProgressModal ? translations.progressBackgroundHint : undefined}
168
170
  />
169
171
  </>
170
172
  );
@@ -18,6 +18,8 @@ export interface AIGenerationFormTranslations {
18
18
  progressTitle?: string;
19
19
  progressMessage?: string;
20
20
  progressHint?: string;
21
+ /** Hint for background generation (e.g., "Continue in background") */
22
+ progressBackgroundHint?: string;
21
23
  presetsTitle?: string;
22
24
  showAdvancedLabel?: string;
23
25
  hideAdvancedLabel?: string;
@@ -57,6 +59,8 @@ export interface AIGenerationFormProps extends PropsWithChildren {
57
59
  // Optional: Generation Progress
58
60
  progress?: number;
59
61
  progressIcon?: string;
62
+ /** Override modal visibility (defaults to isGenerating) */
63
+ isProgressModalVisible?: boolean;
60
64
  /** Callback when user closes the progress modal (for background generation) */
61
65
  onCloseProgressModal?: () => void;
62
66
 
@@ -23,8 +23,8 @@ export interface GenerationProgressContentProps {
23
23
  readonly onDismiss?: () => void;
24
24
  /** Close button in top-right corner for background generation */
25
25
  readonly onClose?: () => void;
26
- /** Close button label (default: "Continue in background") */
27
- readonly closeLabel?: string;
26
+ /** Hint text shown near close button (e.g., "Continue in background") */
27
+ readonly backgroundHint?: string;
28
28
  readonly backgroundColor?: string;
29
29
  readonly textColor?: string;
30
30
  readonly hintColor?: string;
@@ -44,7 +44,7 @@ export const GenerationProgressContent: React.FC<
44
44
  dismissLabel,
45
45
  onDismiss,
46
46
  onClose,
47
- closeLabel,
47
+ backgroundHint,
48
48
  backgroundColor,
49
49
  textColor,
50
50
  hintColor,
@@ -119,6 +119,21 @@ export const GenerationProgressContent: React.FC<
119
119
  </AtomicText>
120
120
  )}
121
121
 
122
+ {/* Background hint - clickable to close and continue in background */}
123
+ {onClose && backgroundHint && (
124
+ <TouchableOpacity
125
+ style={styles.backgroundHintButton}
126
+ onPress={onClose}
127
+ >
128
+ <AtomicText
129
+ type="bodySmall"
130
+ style={[styles.backgroundHintText, { color: tokens.colors.primary }]}
131
+ >
132
+ {backgroundHint}
133
+ </AtomicText>
134
+ </TouchableOpacity>
135
+ )}
136
+
122
137
  {onDismiss && (
123
138
  <TouchableOpacity
124
139
  style={[
@@ -178,6 +193,15 @@ const styles = StyleSheet.create({
178
193
  lineHeight: 18,
179
194
  paddingHorizontal: 8,
180
195
  },
196
+ backgroundHintButton: {
197
+ marginTop: 16,
198
+ paddingVertical: 8,
199
+ paddingHorizontal: 16,
200
+ },
201
+ backgroundHintText: {
202
+ textAlign: "center",
203
+ textDecorationLine: "underline",
204
+ },
181
205
  dismissButton: {
182
206
  marginTop: 16,
183
207
  paddingVertical: 14,
@@ -46,7 +46,7 @@ export const GenerationProgressModal: React.FC<
46
46
  dismissLabel,
47
47
  onDismiss,
48
48
  onClose,
49
- closeLabel,
49
+ backgroundHint,
50
50
  modalBackgroundColor,
51
51
  textColor,
52
52
  hintColor,
@@ -82,7 +82,7 @@ export const GenerationProgressModal: React.FC<
82
82
  dismissLabel={dismissLabel}
83
83
  onDismiss={onDismiss}
84
84
  onClose={onClose}
85
- closeLabel={closeLabel}
85
+ backgroundHint={backgroundHint}
86
86
  backgroundColor={modalBackgroundColor || tokens.colors.surface}
87
87
  textColor={textColor || tokens.colors.textPrimary}
88
88
  hintColor={hintColor || tokens.colors.textTertiary}