@umituz/react-native-ai-generation-content 1.25.8 → 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.8",
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",
@@ -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";
@@ -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
+ };