@umituz/react-native-ai-generation-content 1.25.12 → 1.25.14
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.25.
|
|
3
|
+
"version": "1.25.14",
|
|
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",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* NO feature-specific code here - everything driven by configuration!
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
-
import React, { useMemo, useCallback, useEffect } from "react";
|
|
15
|
+
import React, { useMemo, useCallback, useEffect, useRef } from "react";
|
|
16
16
|
import { View, StyleSheet } from "react-native";
|
|
17
17
|
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
18
18
|
import { useFlow } from "../../../../infrastructure/flow/useFlow";
|
|
@@ -75,6 +75,14 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
75
75
|
initialStepIndex: 0,
|
|
76
76
|
});
|
|
77
77
|
|
|
78
|
+
// Handle progress change - memoized to prevent infinite loops
|
|
79
|
+
const handleProgressChange = useCallback(
|
|
80
|
+
(progress: number) => {
|
|
81
|
+
flow.updateProgress(progress);
|
|
82
|
+
},
|
|
83
|
+
[flow],
|
|
84
|
+
);
|
|
85
|
+
|
|
78
86
|
// Generation hook - handles AI generation automatically
|
|
79
87
|
const generationHook = useWizardGeneration({
|
|
80
88
|
scenario: scenario || { id: featureConfig.id, aiPrompt: "" },
|
|
@@ -84,12 +92,13 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
84
92
|
alertMessages,
|
|
85
93
|
onSuccess: onGenerationComplete,
|
|
86
94
|
onError: onGenerationError,
|
|
87
|
-
onProgressChange:
|
|
88
|
-
flow.updateProgress(progress);
|
|
89
|
-
},
|
|
95
|
+
onProgressChange: handleProgressChange,
|
|
90
96
|
onCreditsExhausted,
|
|
91
97
|
});
|
|
92
98
|
|
|
99
|
+
// Track previous step ID to prevent infinite loops
|
|
100
|
+
const prevStepIdRef = useRef<string>();
|
|
101
|
+
|
|
93
102
|
// DEBUG logging
|
|
94
103
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
95
104
|
console.log("[GenericWizardFlow] Render", {
|
|
@@ -102,18 +111,23 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
102
111
|
}
|
|
103
112
|
|
|
104
113
|
// Notify parent when step changes
|
|
114
|
+
// Only call onStepChange when step ID actually changes (not on every object reference change)
|
|
105
115
|
useEffect(() => {
|
|
106
116
|
if (flow.currentStep && onStepChange) {
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
const currentStepId = flow.currentStep.id;
|
|
118
|
+
// Only notify if step ID changed
|
|
119
|
+
if (prevStepIdRef.current !== currentStepId) {
|
|
120
|
+
prevStepIdRef.current = currentStepId;
|
|
121
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
122
|
+
console.log("[GenericWizardFlow] Step changed", {
|
|
123
|
+
stepId: flow.currentStep.id,
|
|
124
|
+
stepType: flow.currentStep.type,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
onStepChange(flow.currentStep.id, flow.currentStep.type);
|
|
112
128
|
}
|
|
113
|
-
onStepChange(flow.currentStep.id, flow.currentStep.type);
|
|
114
129
|
}
|
|
115
|
-
|
|
116
|
-
}, [flow.currentStep, flow.currentStepIndex]);
|
|
130
|
+
}, [flow.currentStep, flow.currentStepIndex, onStepChange]);
|
|
117
131
|
|
|
118
132
|
// Handle step continue
|
|
119
133
|
const handleStepContinue = useCallback(
|
|
@@ -17,9 +17,12 @@ import type { AlertMessages } from "../../../../presentation/hooks/generation/or
|
|
|
17
17
|
|
|
18
18
|
declare const __DEV__: boolean;
|
|
19
19
|
|
|
20
|
+
export type WizardOutputType = "image" | "video";
|
|
21
|
+
|
|
20
22
|
export interface WizardScenarioData {
|
|
21
23
|
readonly id: string;
|
|
22
24
|
readonly aiPrompt: string;
|
|
25
|
+
readonly outputType?: WizardOutputType; // "image" for couple-future, "video" for ai-hug/kiss
|
|
23
26
|
readonly title?: string;
|
|
24
27
|
readonly description?: string;
|
|
25
28
|
[key: string]: unknown;
|
|
@@ -31,6 +34,14 @@ interface VideoGenerationInput {
|
|
|
31
34
|
readonly prompt: string;
|
|
32
35
|
}
|
|
33
36
|
|
|
37
|
+
interface ImageGenerationInput {
|
|
38
|
+
readonly partnerABase64: string;
|
|
39
|
+
readonly partnerBBase64: string;
|
|
40
|
+
readonly prompt: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
type GenerationInput = VideoGenerationInput | ImageGenerationInput;
|
|
44
|
+
|
|
34
45
|
export interface UseWizardGenerationProps {
|
|
35
46
|
readonly scenario: WizardScenarioData;
|
|
36
47
|
readonly wizardData: Record<string, unknown>;
|
|
@@ -60,6 +71,78 @@ function getVideoFeatureType(scenarioId: string): VideoFeatureType {
|
|
|
60
71
|
return "ai-hug";
|
|
61
72
|
}
|
|
62
73
|
|
|
74
|
+
async function executeImageGeneration(
|
|
75
|
+
input: ImageGenerationInput,
|
|
76
|
+
onProgress?: (progress: number) => void,
|
|
77
|
+
): Promise<{ success: boolean; imageUrl?: string; error?: string }> {
|
|
78
|
+
const { providerRegistry } = await import("../../../../infrastructure/services/provider-registry.service");
|
|
79
|
+
|
|
80
|
+
const provider = providerRegistry.getActiveProvider();
|
|
81
|
+
if (!provider || !provider.isInitialized()) {
|
|
82
|
+
return { success: false, error: "AI provider not initialized" };
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
onProgress?.(5);
|
|
87
|
+
|
|
88
|
+
const formatBase64 = (base64: string): string => {
|
|
89
|
+
return base64.startsWith("data:") ? base64 : `data:image/jpeg;base64,${base64}`;
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const imageUrls = [input.partnerABase64, input.partnerBBase64]
|
|
93
|
+
.filter(Boolean)
|
|
94
|
+
.map(formatBase64);
|
|
95
|
+
|
|
96
|
+
if (imageUrls.length < 2) {
|
|
97
|
+
return { success: false, error: "Two images required" };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
onProgress?.(10);
|
|
101
|
+
|
|
102
|
+
const enhancedPrompt = `A photorealistic image of a couple. The first person @image1 and the second person @image2. ${input.prompt}. High quality, detailed, professional photography.`;
|
|
103
|
+
|
|
104
|
+
const modelInput = {
|
|
105
|
+
image_urls: imageUrls,
|
|
106
|
+
prompt: enhancedPrompt,
|
|
107
|
+
aspect_ratio: "1:1",
|
|
108
|
+
output_format: "jpeg",
|
|
109
|
+
num_images: 1,
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
113
|
+
console.log("[useWizardGeneration] Starting image generation");
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
let lastStatus = "";
|
|
117
|
+
const result = await provider.subscribe("fal-ai/nano-banana", modelInput, {
|
|
118
|
+
timeoutMs: 120000,
|
|
119
|
+
onQueueUpdate: (status) => {
|
|
120
|
+
if (status.status === lastStatus) return;
|
|
121
|
+
lastStatus = status.status;
|
|
122
|
+
if (status.status === "IN_QUEUE") onProgress?.(20);
|
|
123
|
+
else if (status.status === "IN_PROGRESS") onProgress?.(50);
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
onProgress?.(90);
|
|
128
|
+
|
|
129
|
+
const rawResult = result as Record<string, unknown>;
|
|
130
|
+
const data = (rawResult?.data ?? rawResult) as { images?: Array<{ url: string }> };
|
|
131
|
+
const imageUrl = data?.images?.[0]?.url;
|
|
132
|
+
|
|
133
|
+
onProgress?.(100);
|
|
134
|
+
|
|
135
|
+
if (!imageUrl) {
|
|
136
|
+
return { success: false, error: "No image generated" };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { success: true, imageUrl };
|
|
140
|
+
} catch (error) {
|
|
141
|
+
const message = error instanceof Error ? error.message : "Generation failed";
|
|
142
|
+
return { success: false, error: message };
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
63
146
|
async function convertUriToBase64(uri: string): Promise<string> {
|
|
64
147
|
try {
|
|
65
148
|
const base64 = await FileSystem.readAsStringAsync(uri, {
|
|
@@ -77,7 +160,7 @@ async function convertUriToBase64(uri: string): Promise<string> {
|
|
|
77
160
|
async function buildGenerationInput(
|
|
78
161
|
wizardData: Record<string, unknown>,
|
|
79
162
|
scenario: WizardScenarioData,
|
|
80
|
-
): Promise<
|
|
163
|
+
): Promise<GenerationInput | null> {
|
|
81
164
|
const photo1Key = Object.keys(wizardData).find((k) => k.includes("photo_1"));
|
|
82
165
|
const photo2Key = Object.keys(wizardData).find((k) => k.includes("photo_2"));
|
|
83
166
|
|
|
@@ -110,11 +193,23 @@ async function buildGenerationInput(
|
|
|
110
193
|
convertUriToBase64(photo2.uri),
|
|
111
194
|
]);
|
|
112
195
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
196
|
+
const prompt = scenario.aiPrompt || `Generate ${scenario.id} scene`;
|
|
197
|
+
const outputType = scenario.outputType || "video"; // Default to video for backward compatibility
|
|
198
|
+
|
|
199
|
+
// Build input based on output type
|
|
200
|
+
if (outputType === "image") {
|
|
201
|
+
return {
|
|
202
|
+
partnerABase64: photo1Base64,
|
|
203
|
+
partnerBBase64: photo2Base64,
|
|
204
|
+
prompt,
|
|
205
|
+
} as ImageGenerationInput;
|
|
206
|
+
} else {
|
|
207
|
+
return {
|
|
208
|
+
sourceImageBase64: photo1Base64,
|
|
209
|
+
targetImageBase64: photo2Base64,
|
|
210
|
+
prompt,
|
|
211
|
+
} as VideoGenerationInput;
|
|
212
|
+
}
|
|
118
213
|
}
|
|
119
214
|
|
|
120
215
|
export const useWizardGeneration = (
|
|
@@ -133,48 +228,68 @@ export const useWizardGeneration = (
|
|
|
133
228
|
} = props;
|
|
134
229
|
|
|
135
230
|
const hasStarted = useRef(false);
|
|
136
|
-
const lastInputRef = useRef<
|
|
231
|
+
const lastInputRef = useRef<GenerationInput | null>(null);
|
|
137
232
|
const repository = useMemo(() => createCreationsRepository("creations"), []);
|
|
138
233
|
const videoFeatureType = useMemo(() => getVideoFeatureType(scenario.id), [scenario.id]);
|
|
234
|
+
const outputType = scenario.outputType || "video";
|
|
235
|
+
|
|
236
|
+
type GenerationResult = { videoUrl: string } | { imageUrl: string };
|
|
139
237
|
|
|
140
|
-
const strategy: GenerationStrategy<
|
|
238
|
+
const strategy: GenerationStrategy<GenerationInput, GenerationResult> = useMemo(
|
|
141
239
|
() => ({
|
|
142
240
|
execute: async (input, onProgress) => {
|
|
143
241
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
144
242
|
console.log("[useWizardGeneration] Executing generation", {
|
|
145
243
|
scenarioId: scenario.id,
|
|
146
|
-
|
|
244
|
+
outputType,
|
|
147
245
|
});
|
|
148
246
|
}
|
|
149
247
|
|
|
150
248
|
lastInputRef.current = input;
|
|
151
249
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
250
|
+
// Execute based on output type
|
|
251
|
+
if (outputType === "image") {
|
|
252
|
+
const imageInput = input as ImageGenerationInput;
|
|
253
|
+
const result = await executeImageGeneration(imageInput, onProgress);
|
|
254
|
+
|
|
255
|
+
if (!result.success || !result.imageUrl) {
|
|
256
|
+
throw new Error(result.error || "Image generation failed");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return { imageUrl: result.imageUrl };
|
|
260
|
+
} else {
|
|
261
|
+
const videoInput = input as VideoGenerationInput;
|
|
262
|
+
const result = await executeVideoFeature(
|
|
263
|
+
videoFeatureType,
|
|
264
|
+
{
|
|
265
|
+
sourceImageBase64: videoInput.sourceImageBase64,
|
|
266
|
+
targetImageBase64: videoInput.targetImageBase64,
|
|
267
|
+
prompt: videoInput.prompt,
|
|
268
|
+
},
|
|
269
|
+
{ onProgress },
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
if (!result.success || !result.videoUrl) {
|
|
273
|
+
throw new Error(result.error || "Video generation failed");
|
|
274
|
+
}
|
|
165
275
|
|
|
166
|
-
|
|
276
|
+
return { videoUrl: result.videoUrl };
|
|
277
|
+
}
|
|
167
278
|
},
|
|
168
279
|
getCreditCost: () => 1,
|
|
169
280
|
save: async (result, uid) => {
|
|
170
281
|
const input = lastInputRef.current;
|
|
171
|
-
if (!input
|
|
282
|
+
if (!input) return;
|
|
283
|
+
|
|
284
|
+
const videoResult = result as { videoUrl?: string };
|
|
285
|
+
const imageResult = result as { imageUrl?: string };
|
|
172
286
|
|
|
173
287
|
const creation = {
|
|
174
|
-
videoUrl:
|
|
288
|
+
videoUrl: videoResult.videoUrl,
|
|
289
|
+
imageUrl: imageResult.imageUrl,
|
|
175
290
|
scenarioId: scenario.id,
|
|
176
291
|
scenarioTitle: scenario.title || scenario.id,
|
|
177
|
-
prompt: input.prompt,
|
|
292
|
+
prompt: (input as VideoGenerationInput).prompt,
|
|
178
293
|
createdAt: Date.now(),
|
|
179
294
|
};
|
|
180
295
|
|
|
@@ -185,7 +300,7 @@ export const useWizardGeneration = (
|
|
|
185
300
|
}
|
|
186
301
|
},
|
|
187
302
|
}),
|
|
188
|
-
[scenario, videoFeatureType, repository],
|
|
303
|
+
[scenario, videoFeatureType, repository, outputType],
|
|
189
304
|
);
|
|
190
305
|
|
|
191
306
|
const { generate, isGenerating, progress } = useGenerationOrchestrator(strategy, {
|