@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 +1 -1
- package/src/domains/scenarios/configs/wizard-configs.ts +11 -11
- package/src/domains/wizard/index.ts +7 -0
- package/src/domains/wizard/infrastructure/renderers/step-renderer.tsx +1 -0
- package/src/domains/wizard/presentation/components/GenericWizardFlow.tsx +29 -0
- package/src/domains/wizard/presentation/hooks/useWizardGeneration.ts +261 -0
- package/src/domains/wizard/presentation/steps/PhotoUploadStep.tsx +0 -1
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.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
|
|
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
|
|
168
|
-
*
|
|
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.
|
|
178
|
-
subtitleKey: "faceSwap.
|
|
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.
|
|
187
|
-
subtitleKey: "faceSwap.
|
|
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={{
|