@umituz/react-native-ai-generation-content 1.25.6 → 1.25.10

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.6",
3
+ "version": "1.25.10",
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",
@@ -86,7 +86,7 @@ export const detectFeatureType = (scenarioId: string): FeatureType => {
86
86
 
87
87
  /**
88
88
  * Config Factory for COUPLE features (2 photos)
89
- * Generic numbered steps - labels come from translations
89
+ * Generic sequential labels for two-person scenarios
90
90
  */
91
91
  const createCoupleConfig = (scenarioId: string): WizardFeatureConfig => ({
92
92
  id: scenarioId,
@@ -95,8 +95,8 @@ const createCoupleConfig = (scenarioId: string): WizardFeatureConfig => ({
95
95
  {
96
96
  id: "photo_1",
97
97
  type: "photo_upload",
98
- titleKey: "photoUpload.step1.title",
99
- subtitleKey: "photoUpload.step1.subtitle",
98
+ titleKey: "photoUpload.couple.step1.title",
99
+ subtitleKey: "photoUpload.couple.step1.subtitle",
100
100
  showFaceDetection: true,
101
101
  showPhotoTips: true,
102
102
  required: true,
@@ -104,8 +104,8 @@ const createCoupleConfig = (scenarioId: string): WizardFeatureConfig => ({
104
104
  {
105
105
  id: "photo_2",
106
106
  type: "photo_upload",
107
- titleKey: "photoUpload.step2.title",
108
- subtitleKey: "photoUpload.step2.subtitle",
107
+ titleKey: "photoUpload.couple.step2.title",
108
+ subtitleKey: "photoUpload.couple.step2.subtitle",
109
109
  showFaceDetection: true,
110
110
  showPhotoTips: true,
111
111
  required: true,
@@ -164,8 +164,8 @@ const createTextBasedConfig = (scenarioId: string): WizardFeatureConfig => ({
164
164
  });
165
165
 
166
166
  /**
167
- * Config Factory for FACE_SWAP features (2 photos, different context)
168
- * Generic numbered steps - labels come from translations
167
+ * Config Factory for FACE_SWAP features (2 photos)
168
+ * Specific labels for face replacement scenarios
169
169
  */
170
170
  const createFaceSwapConfig = (scenarioId: string): WizardFeatureConfig => ({
171
171
  id: scenarioId,
@@ -174,8 +174,8 @@ const createFaceSwapConfig = (scenarioId: string): WizardFeatureConfig => ({
174
174
  {
175
175
  id: "photo_1",
176
176
  type: "photo_upload",
177
- titleKey: "faceSwap.source.title",
178
- subtitleKey: "faceSwap.source.subtitle",
177
+ titleKey: "photoUpload.faceSwap.step1.title",
178
+ subtitleKey: "photoUpload.faceSwap.step1.subtitle",
179
179
  showFaceDetection: true,
180
180
  showPhotoTips: true,
181
181
  required: true,
@@ -183,8 +183,8 @@ const createFaceSwapConfig = (scenarioId: string): WizardFeatureConfig => ({
183
183
  {
184
184
  id: "photo_2",
185
185
  type: "photo_upload",
186
- titleKey: "faceSwap.target.title",
187
- subtitleKey: "faceSwap.target.subtitle",
186
+ titleKey: "photoUpload.faceSwap.step2.title",
187
+ subtitleKey: "photoUpload.faceSwap.step2.subtitle",
188
188
  showFaceDetection: false,
189
189
  showPhotoTips: true,
190
190
  required: true,
@@ -63,3 +63,10 @@ export type {
63
63
  PhotoUploadConfig,
64
64
  PhotoUploadTranslations,
65
65
  } from "./presentation/hooks/usePhotoUploadState";
66
+
67
+ export { useWizardGeneration } from "./presentation/hooks/useWizardGeneration";
68
+ export type {
69
+ UseWizardGenerationProps,
70
+ UseWizardGenerationReturn,
71
+ WizardScenarioData,
72
+ } from "./presentation/hooks/useWizardGeneration";
@@ -50,6 +50,7 @@ export const renderStep = (props: StepRendererProps): React.ReactElement | null
50
50
  const photoConfig = wizardConfig as PhotoUploadStepConfig;
51
51
  return (
52
52
  <PhotoUploadStep
53
+ key={step.id}
53
54
  config={photoConfig}
54
55
  onContinue={(imageData) => {
55
56
  onContinue({ [`photo_${step.id}`]: imageData });
@@ -21,11 +21,19 @@ import type { StepDefinition } from "../../../../domain/entities/flow-config.typ
21
21
  import { renderStep } from "../../infrastructure/renderers/step-renderer";
22
22
  import type { WizardFeatureConfig } from "../../domain/entities/wizard-config.types";
23
23
  import { buildFlowStepsFromWizard } from "../../infrastructure/builders/dynamic-step-builder";
24
+ import { useWizardGeneration, type WizardScenarioData } from "../hooks/useWizardGeneration";
25
+ import type { AlertMessages } from "../../../../presentation/hooks/generation/orchestrator.types";
24
26
 
25
27
  export interface GenericWizardFlowProps {
26
28
  readonly featureConfig: WizardFeatureConfig;
29
+ readonly scenario?: WizardScenarioData;
30
+ readonly userId?: string;
31
+ readonly alertMessages?: AlertMessages;
27
32
  readonly onStepChange?: (stepId: string, stepType: StepType | string) => void;
28
33
  readonly onGenerationStart?: (data: Record<string, unknown>, proceedToGenerating: () => void) => void;
34
+ readonly onGenerationComplete?: (result: unknown) => void;
35
+ readonly onGenerationError?: (error: string) => void;
36
+ readonly onCreditsExhausted?: () => void;
29
37
  readonly onBack?: () => void;
30
38
  readonly t: (key: string) => string;
31
39
  readonly translations?: Record<string, string>;
@@ -36,8 +44,14 @@ export interface GenericWizardFlowProps {
36
44
 
37
45
  export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
38
46
  featureConfig,
47
+ scenario,
48
+ userId,
49
+ alertMessages,
39
50
  onStepChange,
40
51
  onGenerationStart,
52
+ onGenerationComplete,
53
+ onGenerationError,
54
+ onCreditsExhausted,
41
55
  onBack,
42
56
  t,
43
57
  translations,
@@ -61,6 +75,21 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
61
75
  initialStepIndex: 0,
62
76
  });
63
77
 
78
+ // Generation hook - handles AI generation automatically
79
+ const generationHook = useWizardGeneration({
80
+ scenario: scenario || { id: featureConfig.id, aiPrompt: "" },
81
+ wizardData: flow.customData,
82
+ userId,
83
+ isGeneratingStep: flow.currentStep?.type === StepType.GENERATING,
84
+ alertMessages,
85
+ onSuccess: onGenerationComplete,
86
+ onError: onGenerationError,
87
+ onProgressChange: (progress) => {
88
+ flow.setGenerationProgress(progress);
89
+ },
90
+ onCreditsExhausted,
91
+ });
92
+
64
93
  // DEBUG logging
65
94
  if (typeof __DEV__ !== "undefined" && __DEV__) {
66
95
  console.log("[GenericWizardFlow] Render", {
@@ -0,0 +1,261 @@
1
+ /**
2
+ * useWizardGeneration Hook
3
+ * Generic generation hook for ANY wizard-based scenario
4
+ * Handles video generation for couple features (ai-hug, ai-kiss, etc.)
5
+ */
6
+
7
+ import { useEffect, useRef, useMemo, useCallback } from "react";
8
+ import * as FileSystem from "expo-file-system";
9
+ import {
10
+ useGenerationOrchestrator,
11
+ type GenerationStrategy,
12
+ } from "../../../../presentation/hooks/generation";
13
+ import { executeVideoFeature } from "../../../../infrastructure/services/video-feature-executor.service";
14
+ import { createCreationsRepository } from "../../../../domains/creations/infrastructure/adapters";
15
+ import type { VideoFeatureType } from "../../../../domain/interfaces";
16
+ import type { AlertMessages } from "../../../../presentation/hooks/generation/orchestrator.types";
17
+
18
+ declare const __DEV__: boolean;
19
+
20
+ export interface WizardScenarioData {
21
+ readonly id: string;
22
+ readonly aiPrompt: string;
23
+ readonly title?: string;
24
+ readonly description?: string;
25
+ [key: string]: unknown;
26
+ }
27
+
28
+ interface VideoGenerationInput {
29
+ readonly sourceImageBase64: string;
30
+ readonly targetImageBase64: string;
31
+ readonly prompt: string;
32
+ }
33
+
34
+ export interface UseWizardGenerationProps {
35
+ readonly scenario: WizardScenarioData;
36
+ readonly wizardData: Record<string, unknown>;
37
+ readonly userId?: string;
38
+ readonly isGeneratingStep: boolean;
39
+ readonly alertMessages?: AlertMessages;
40
+ readonly onSuccess?: (result: unknown) => void;
41
+ readonly onError?: (error: string) => void;
42
+ readonly onProgressChange?: (progress: number) => void;
43
+ readonly onCreditsExhausted?: () => void;
44
+ }
45
+
46
+ export interface UseWizardGenerationReturn {
47
+ readonly isGenerating: boolean;
48
+ readonly progress: number;
49
+ }
50
+
51
+ function getVideoFeatureType(scenarioId: string): VideoFeatureType {
52
+ const id = scenarioId.toLowerCase();
53
+
54
+ if (id.includes("kiss")) return "ai-kiss";
55
+ if (id.includes("hug")) return "ai-hug";
56
+
57
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
58
+ console.warn(`[useWizardGeneration] Unknown scenario type "${scenarioId}", defaulting to ai-hug`);
59
+ }
60
+ return "ai-hug";
61
+ }
62
+
63
+ async function convertUriToBase64(uri: string): Promise<string> {
64
+ try {
65
+ const base64 = await FileSystem.readAsStringAsync(uri, {
66
+ encoding: FileSystem.EncodingType.Base64,
67
+ });
68
+ return base64;
69
+ } catch (error) {
70
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
71
+ console.error("[useWizardGeneration] Base64 conversion failed:", error);
72
+ }
73
+ throw new Error("Failed to convert image to base64");
74
+ }
75
+ }
76
+
77
+ async function buildGenerationInput(
78
+ wizardData: Record<string, unknown>,
79
+ scenario: WizardScenarioData,
80
+ ): Promise<VideoGenerationInput | null> {
81
+ const photo1Key = Object.keys(wizardData).find((k) => k.includes("photo_1"));
82
+ const photo2Key = Object.keys(wizardData).find((k) => k.includes("photo_2"));
83
+
84
+ if (!photo1Key || !photo2Key) {
85
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
86
+ console.error("[useWizardGeneration] Missing photos in wizard data", {
87
+ keys: Object.keys(wizardData),
88
+ hasPhoto1: !!photo1Key,
89
+ hasPhoto2: !!photo2Key,
90
+ });
91
+ }
92
+ return null;
93
+ }
94
+
95
+ const photo1 = wizardData[photo1Key] as { uri?: string; base64?: string };
96
+ const photo2 = wizardData[photo2Key] as { uri?: string; base64?: string };
97
+
98
+ if (!photo1?.uri || !photo2?.uri) {
99
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
100
+ console.error("[useWizardGeneration] Photos missing URI", {
101
+ photo1HasUri: !!photo1?.uri,
102
+ photo2HasUri: !!photo2?.uri,
103
+ });
104
+ }
105
+ return null;
106
+ }
107
+
108
+ const [photo1Base64, photo2Base64] = await Promise.all([
109
+ convertUriToBase64(photo1.uri),
110
+ convertUriToBase64(photo2.uri),
111
+ ]);
112
+
113
+ return {
114
+ sourceImageBase64: photo1Base64,
115
+ targetImageBase64: photo2Base64,
116
+ prompt: scenario.aiPrompt || `Generate ${scenario.id} scene`,
117
+ };
118
+ }
119
+
120
+ export const useWizardGeneration = (
121
+ props: UseWizardGenerationProps,
122
+ ): UseWizardGenerationReturn => {
123
+ const {
124
+ scenario,
125
+ wizardData,
126
+ userId,
127
+ isGeneratingStep,
128
+ alertMessages,
129
+ onSuccess,
130
+ onError,
131
+ onProgressChange,
132
+ onCreditsExhausted,
133
+ } = props;
134
+
135
+ const hasStarted = useRef(false);
136
+ const lastInputRef = useRef<VideoGenerationInput | null>(null);
137
+ const repository = useMemo(() => createCreationsRepository("creations"), []);
138
+ const videoFeatureType = useMemo(() => getVideoFeatureType(scenario.id), [scenario.id]);
139
+
140
+ const strategy: GenerationStrategy<VideoGenerationInput, { videoUrl: string }> = useMemo(
141
+ () => ({
142
+ execute: async (input, onProgress) => {
143
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
144
+ console.log("[useWizardGeneration] Executing generation", {
145
+ scenarioId: scenario.id,
146
+ featureType: videoFeatureType,
147
+ });
148
+ }
149
+
150
+ lastInputRef.current = input;
151
+
152
+ const result = await executeVideoFeature(
153
+ videoFeatureType,
154
+ {
155
+ sourceImageBase64: input.sourceImageBase64,
156
+ targetImageBase64: input.targetImageBase64,
157
+ prompt: input.prompt,
158
+ },
159
+ { onProgress },
160
+ );
161
+
162
+ if (!result.success || !result.videoUrl) {
163
+ throw new Error(result.error || "Video generation failed");
164
+ }
165
+
166
+ return { videoUrl: result.videoUrl };
167
+ },
168
+ getCreditCost: () => 1,
169
+ save: async (result, uid) => {
170
+ const input = lastInputRef.current;
171
+ if (!input || !result.videoUrl) return;
172
+
173
+ const creation = {
174
+ videoUrl: result.videoUrl,
175
+ scenarioId: scenario.id,
176
+ scenarioTitle: scenario.title || scenario.id,
177
+ prompt: input.prompt,
178
+ createdAt: Date.now(),
179
+ };
180
+
181
+ await repository.create(uid, creation);
182
+
183
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
184
+ console.log("[useWizardGeneration] Creation saved");
185
+ }
186
+ },
187
+ }),
188
+ [scenario, videoFeatureType, repository],
189
+ );
190
+
191
+ const { generate, isGenerating, progress } = useGenerationOrchestrator(strategy, {
192
+ userId,
193
+ alertMessages,
194
+ onCreditsExhausted,
195
+ onSuccess: useCallback(
196
+ (result) => {
197
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
198
+ console.log("[useWizardGeneration] Success");
199
+ }
200
+ onSuccess?.(result);
201
+ },
202
+ [onSuccess],
203
+ ),
204
+ onError: useCallback(
205
+ (err) => {
206
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
207
+ console.log("[useWizardGeneration] Error:", err.message);
208
+ }
209
+ onError?.(err.message);
210
+ },
211
+ [onError],
212
+ ),
213
+ });
214
+
215
+ useEffect(() => {
216
+ if (onProgressChange) {
217
+ onProgressChange(progress);
218
+ }
219
+ }, [progress, onProgressChange]);
220
+
221
+ useEffect(() => {
222
+ if (isGeneratingStep && !hasStarted.current && !isGenerating) {
223
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
224
+ console.log("[useWizardGeneration] Starting generation", {
225
+ scenarioId: scenario.id,
226
+ wizardDataKeys: Object.keys(wizardData),
227
+ });
228
+ }
229
+
230
+ buildGenerationInput(wizardData, scenario)
231
+ .then((input) => {
232
+ if (!input) {
233
+ const error = "Failed to build generation input";
234
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
235
+ console.error("[useWizardGeneration]", error);
236
+ }
237
+ onError?.(error);
238
+ return;
239
+ }
240
+
241
+ generate(input);
242
+ hasStarted.current = true;
243
+ })
244
+ .catch((error) => {
245
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
246
+ console.error("[useWizardGeneration] Input build error:", error);
247
+ }
248
+ onError?.(error.message || "Failed to prepare generation");
249
+ });
250
+ }
251
+
252
+ if (!isGeneratingStep && hasStarted.current) {
253
+ hasStarted.current = false;
254
+ }
255
+ }, [isGeneratingStep, scenario, wizardData, isGenerating, generate, onError]);
256
+
257
+ return {
258
+ isGenerating,
259
+ progress,
260
+ };
261
+ };
@@ -47,7 +47,6 @@ export const PhotoUploadStep: React.FC<PhotoUploadStepProps> = ({
47
47
  maxFileSize: t("common.errors.max_file_size"),
48
48
  error: t("common.error"),
49
49
  uploadFailed: t("common.errors.upload_failed"),
50
- aiDisclosure: t("photoUpload.aiDisclosure"),
51
50
  }}
52
51
  t={t}
53
52
  config={{