@umituz/react-native-ai-generation-content 1.26.48 → 1.26.49

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.26.48",
3
+ "version": "1.26.49",
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",
@@ -17,17 +17,15 @@ import { View, StyleSheet } from "react-native";
17
17
  import { useAppDesignTokens } from "@umituz/react-native-design-system";
18
18
  import { useFlow } from "../../../infrastructure/flow/useFlow";
19
19
  import { StepType, type StepDefinition } from "../../../../../domain/entities/flow-config.types";
20
- import type { WizardFeatureConfig, WizardStepConfig } from "../../domain/entities/wizard-config.types";
20
+ import type { WizardFeatureConfig } from "../../domain/entities/wizard-config.types";
21
21
  import { buildFlowStepsFromWizard } from "../../infrastructure/builders/dynamic-step-builder";
22
22
  import { useWizardGeneration, type WizardScenarioData } from "../hooks/useWizardGeneration";
23
23
  import type { AlertMessages } from "../../../../../presentation/hooks/generation/types";
24
24
  import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
25
- import { GenericPhotoUploadScreen } from "../screens/GenericPhotoUploadScreen";
26
- import { GeneratingScreen } from "../screens/GeneratingScreen";
27
- import { ScenarioPreviewScreen } from "../../../../scenarios/presentation/screens/ScenarioPreviewScreen";
28
- import { ResultPreviewScreen } from "../../../../result-preview/presentation/components/ResultPreviewScreen";
29
- import { useResultActions } from "../../../../result-preview/presentation/hooks/useResultActions";
30
25
  import type { Creation } from "../../../../creations/domain/entities/Creation";
26
+ import { useResultActions } from "../../../../result-preview/presentation/hooks/useResultActions";
27
+ import { validateScenario } from "../utilities/validateScenario";
28
+ import { WizardStepRenderer } from "./WizardStepRenderer";
31
29
 
32
30
  export interface GenericWizardFlowProps {
33
31
  readonly featureConfig: WizardFeatureConfig;
@@ -68,6 +66,7 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
68
66
  }) => {
69
67
  const tokens = useAppDesignTokens();
70
68
  const [currentCreation, setCurrentCreation] = useState<Creation | null>(null);
69
+ const prevStepIdRef = useRef<string | undefined>(undefined);
71
70
 
72
71
  const flowSteps = useMemo<StepDefinition[]>(() => {
73
72
  return buildFlowStepsFromWizard(featureConfig, {
@@ -76,13 +75,7 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
76
75
  });
77
76
  }, [featureConfig]);
78
77
 
79
- // Initialize flow and destructure to prevent infinite loops
80
- const flow = useFlow({
81
- steps: flowSteps,
82
- initialStepIndex: 0,
83
- });
84
-
85
- // Destructure flow to get stable references for useCallback dependencies
78
+ const flow = useFlow({ steps: flowSteps, initialStepIndex: 0 });
86
79
  const {
87
80
  currentStep,
88
81
  currentStepIndex,
@@ -101,72 +94,22 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
101
94
  imageUrl: resultImageUrl,
102
95
  });
103
96
 
104
- const handleProgressChange = useCallback(
105
- (progress: number) => {
106
- updateProgress(progress);
107
- },
108
- [updateProgress],
109
- );
110
-
111
- const handleGenerationComplete = useCallback(
112
- (result: unknown) => {
113
- if (typeof __DEV__ !== "undefined" && __DEV__) {
114
- console.log("[GenericWizardFlow] Generation completed, saving result and advancing to result preview");
115
- }
116
- setResult(result);
117
- setCurrentCreation(result as Creation);
118
- nextStep();
119
- onGenerationComplete?.(result);
120
- },
121
- [setResult, nextStep, onGenerationComplete],
122
- );
123
-
124
- // Validate scenario - NO FALLBACK, aiPrompt is REQUIRED
125
- const validatedScenario = useMemo(() => {
126
- if (typeof __DEV__ !== "undefined" && __DEV__) {
127
- console.log("[GenericWizardFlow] Validating scenario", {
128
- hasScenario: !!scenario,
129
- scenarioId: scenario?.id,
130
- hasAiPrompt: !!scenario?.aiPrompt,
131
- aiPromptValue: scenario?.aiPrompt,
132
- aiPromptLength: scenario?.aiPrompt?.length,
133
- hasModel: !!scenario?.model,
134
- scenarioModel: scenario?.model,
135
- outputType: scenario?.outputType,
136
- fullScenario: JSON.stringify(scenario, null, 2),
137
- });
138
- }
139
-
140
- if (!scenario || !scenario.id) {
141
- throw new Error("[GenericWizardFlow] Scenario is required");
142
- }
97
+ const validatedScenario = useMemo(() => validateScenario(scenario), [scenario]);
143
98
 
144
- if (!scenario.aiPrompt || scenario.aiPrompt.trim() === "") {
145
- if (typeof __DEV__ !== "undefined" && __DEV__) {
146
- console.error("[GenericWizardFlow] CRITICAL: Scenario missing aiPrompt!", {
147
- scenarioId: scenario.id,
148
- aiPrompt: scenario.aiPrompt,
149
- fullScenario: scenario,
150
- });
151
- }
152
- throw new Error(`[GenericWizardFlow] Scenario "${scenario.id}" must have aiPrompt field`);
153
- }
99
+ const handleProgressChange = useCallback((progress: number) => {
100
+ updateProgress(progress);
101
+ }, [updateProgress]);
154
102
 
103
+ const handleGenerationComplete = useCallback((result: unknown) => {
155
104
  if (typeof __DEV__ !== "undefined" && __DEV__) {
156
- console.log("[GenericWizardFlow] Scenario validation passed", {
157
- scenarioId: scenario.id,
158
- model: scenario.model,
159
- outputType: scenario.outputType,
160
- promptLength: scenario.aiPrompt.length,
161
- promptPreview: scenario.aiPrompt.substring(0, 100),
162
- });
105
+ console.log("[GenericWizardFlow] Generation completed");
163
106
  }
107
+ setResult(result);
108
+ setCurrentCreation(result as Creation);
109
+ nextStep();
110
+ onGenerationComplete?.(result);
111
+ }, [setResult, nextStep, onGenerationComplete]);
164
112
 
165
- return scenario;
166
- }, [scenario]);
167
-
168
- // Generation hook - handles AI generation automatically
169
- // Note: Hook is used for its side effects (automatic generation)
170
113
  useWizardGeneration({
171
114
  scenario: validatedScenario,
172
115
  wizardData: customData,
@@ -179,40 +122,16 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
179
122
  onCreditsExhausted,
180
123
  });
181
124
 
182
- // Track previous step ID to prevent infinite loops
183
- const prevStepIdRef = useRef<string | undefined>(undefined);
184
-
185
- // DEBUG logging
186
- if (typeof __DEV__ !== "undefined" && __DEV__) {
187
- console.log("[GenericWizardFlow] Render", {
188
- featureId: featureConfig.id,
189
- currentStepId: currentStep?.id,
190
- currentStepType: currentStep?.type,
191
- stepIndex: currentStepIndex,
192
- totalSteps: flowSteps.length,
193
- });
194
- }
195
-
196
- // Notify parent when step changes
197
- // Only call onStepChange when step ID actually changes (not on every object reference change)
198
125
  useEffect(() => {
199
126
  if (currentStep && onStepChange) {
200
127
  const currentStepId = currentStep.id;
201
- // Only notify if step ID changed
202
128
  if (prevStepIdRef.current !== currentStepId) {
203
129
  prevStepIdRef.current = currentStepId;
204
- if (typeof __DEV__ !== "undefined" && __DEV__) {
205
- console.log("[GenericWizardFlow] Step changed", {
206
- stepId: currentStep.id,
207
- stepType: currentStep.type,
208
- });
209
- }
210
130
  onStepChange(currentStep.id, currentStep.type);
211
131
  }
212
132
  }
213
133
  }, [currentStep, currentStepIndex, onStepChange]);
214
134
 
215
- // Handle back
216
135
  const handleBack = useCallback(() => {
217
136
  if (currentStepIndex === 0) {
218
137
  onBack?.();
@@ -221,18 +140,12 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
221
140
  }
222
141
  }, [currentStepIndex, previousStep, onBack]);
223
142
 
224
- // Handle photo continue - saves photo and moves to next step
225
143
  const handlePhotoContinue = useCallback((stepId: string, image: UploadedImage) => {
226
144
  setCustomData(stepId, image);
227
145
 
228
- // Check if this is the last step before generating
229
146
  if (currentStepIndex === flowSteps.length - 2) {
230
- // Next step is GENERATING - call onGenerationStart
231
147
  if (onGenerationStart) {
232
148
  onGenerationStart({ ...customData, [stepId]: image }, () => {
233
- if (typeof __DEV__ !== "undefined" && __DEV__) {
234
- console.log("[GenericWizardFlow] Proceeding to GENERATING step");
235
- }
236
149
  nextStep();
237
150
  });
238
151
  }
@@ -242,152 +155,27 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
242
155
  nextStep();
243
156
  }, [currentStepIndex, flowSteps.length, customData, setCustomData, nextStep, onGenerationStart]);
244
157
 
245
- // Render current step
246
- const renderCurrentStep = useCallback(() => {
247
- const step = currentStep;
248
- if (!step) {
249
- if (typeof __DEV__ !== "undefined" && __DEV__) {
250
- console.warn("[GenericWizardFlow] No current step!");
251
- }
252
- return null;
253
- }
254
-
255
- if (typeof __DEV__ !== "undefined" && __DEV__) {
256
- console.log("[GenericWizardFlow] Rendering step", {
257
- stepId: step.id,
258
- stepType: step.type,
259
- });
260
- }
261
-
262
- switch (step.type) {
263
- case StepType.SCENARIO_PREVIEW: {
264
- if (renderPreview) {
265
- return renderPreview(nextStep);
266
- }
267
- return (
268
- <ScenarioPreviewScreen
269
- scenario={scenario}
270
- translations={{
271
- continueButton: t("common.continue"),
272
- whatToExpect: t("scenarioPreview.whatToExpect"),
273
- }}
274
- onContinue={nextStep}
275
- onBack={handleBack}
276
- t={t}
277
- />
278
- );
279
- }
280
-
281
- case StepType.GENERATING: {
282
- if (renderGenerating) {
283
- return renderGenerating(generationProgress);
284
- }
285
- return (
286
- <GeneratingScreen
287
- progress={generationProgress}
288
- scenario={scenario}
289
- t={t}
290
- />
291
- );
292
- }
293
-
294
- case StepType.RESULT_PREVIEW: {
295
- if (renderResult) {
296
- return renderResult(generationResult);
297
- }
298
- const creation = generationResult as Creation;
299
- const imageUrl = creation?.output?.imageUrl || creation?.uri || "";
300
- if (!imageUrl) return null;
301
- return (
302
- <ResultPreviewScreen
303
- imageUrl={imageUrl}
304
- isSaving={isSaving}
305
- isSharing={isSharing}
306
- onDownload={handleDownload}
307
- onShare={handleShare}
308
- onTryAgain={onTryAgain || onBack || (() => {})}
309
- onNavigateBack={onTryAgain || onBack || (() => {})}
310
- translations={{
311
- title: t("generation.result.title"),
312
- yourResult: t("generation.result.yourResult"),
313
- saveButton: t("generation.result.save"),
314
- saving: t("generation.result.saving"),
315
- shareButton: t("generation.result.share"),
316
- sharing: t("generation.result.sharing"),
317
- tryAnother: t("generation.result.tryAnother"),
318
- }}
319
- />
320
- );
321
- }
322
-
323
- case StepType.PARTNER_UPLOAD: {
324
- // Get wizard step config
325
- const wizardConfig = step.config as WizardStepConfig;
326
-
327
- // Use titleKey from config, fallback to step-specific translation key
328
- const titleKey = wizardConfig?.titleKey || `wizard.steps.${step.id}.title`;
329
- const title = t(titleKey);
330
-
331
- // Subtitle from config
332
- const subtitleKey = wizardConfig?.subtitleKey || `wizard.steps.${step.id}.subtitle`;
333
- const subtitle = t(subtitleKey);
334
-
335
- // Get existing photo for this step from customData
336
- const existingPhoto = customData[step.id] as UploadedImage | undefined;
337
-
338
- return (
339
- <GenericPhotoUploadScreen
340
- translations={{
341
- title,
342
- subtitle,
343
- continue: t("common.continue"),
344
- tapToUpload: t("photoUpload.tapToUpload"),
345
- selectPhoto: t("photoUpload.selectPhoto"),
346
- change: t("common.change"),
347
- fileTooLarge: t("common.errors.file_too_large"),
348
- maxFileSize: t("common.errors.max_file_size"),
349
- error: t("common.error"),
350
- uploadFailed: t("common.errors.upload_failed"),
351
- }}
352
- t={t}
353
- onBack={handleBack}
354
- onContinue={(image) => handlePhotoContinue(step.id, image)}
355
- existingImage={existingPhoto}
356
- />
357
- );
358
- }
359
-
360
- default:
361
- // Other step types should be handled by custom render props
362
- if (typeof __DEV__ !== "undefined" && __DEV__) {
363
- console.warn("[GenericWizardFlow] Unhandled step type", { stepType: step.type });
364
- }
365
- return null;
366
- }
367
- }, [
368
- currentStep,
369
- customData,
370
- generationProgress,
371
- generationResult,
372
- nextStep,
373
- renderPreview,
374
- renderGenerating,
375
- renderResult,
376
- handlePhotoContinue,
377
- handleBack,
378
- isSaving,
379
- isSharing,
380
- handleDownload,
381
- handleShare,
382
- onTryAgain,
383
- onBack,
384
- scenario,
385
- t,
386
- ]);
387
-
388
158
  return (
389
159
  <View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
390
- {renderCurrentStep()}
160
+ <WizardStepRenderer
161
+ step={currentStep}
162
+ scenario={scenario}
163
+ customData={customData}
164
+ generationProgress={generationProgress}
165
+ generationResult={generationResult}
166
+ isSaving={isSaving}
167
+ isSharing={isSharing}
168
+ onNext={nextStep}
169
+ onBack={handleBack}
170
+ onPhotoContinue={handlePhotoContinue}
171
+ onDownload={handleDownload}
172
+ onShare={handleShare}
173
+ onTryAgain={onTryAgain}
174
+ t={t}
175
+ renderPreview={renderPreview}
176
+ renderGenerating={renderGenerating}
177
+ renderResult={renderResult}
178
+ />
391
179
  </View>
392
180
  );
393
181
  };
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Wizard Step Renderer Component
3
+ * Renders the appropriate screen based on current step type
4
+ */
5
+
6
+ import React from "react";
7
+ import { StepType, type StepDefinition } from "../../../../../domain/entities/flow-config.types";
8
+ import type { WizardStepConfig } from "../../domain/entities/wizard-config.types";
9
+ import type { WizardScenarioData } from "../hooks/useWizardGeneration";
10
+ import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
11
+ import type { Creation } from "../../../../creations/domain/entities/Creation";
12
+ import { GenericPhotoUploadScreen } from "../screens/GenericPhotoUploadScreen";
13
+ import { GeneratingScreen } from "../screens/GeneratingScreen";
14
+ import { ScenarioPreviewScreen } from "../../../../scenarios/presentation/screens/ScenarioPreviewScreen";
15
+ import { ResultPreviewScreen } from "../../../../result-preview/presentation/components/ResultPreviewScreen";
16
+
17
+ export interface WizardStepRendererProps {
18
+ readonly step: StepDefinition | undefined;
19
+ readonly scenario?: WizardScenarioData;
20
+ readonly customData: Record<string, unknown>;
21
+ readonly generationProgress: number;
22
+ readonly generationResult: unknown;
23
+ readonly isSaving: boolean;
24
+ readonly isSharing: boolean;
25
+ readonly onNext: () => void;
26
+ readonly onBack: () => void;
27
+ readonly onPhotoContinue: (stepId: string, image: UploadedImage) => void;
28
+ readonly onDownload: () => void;
29
+ readonly onShare: () => void;
30
+ readonly onTryAgain?: () => void;
31
+ readonly t: (key: string) => string;
32
+ readonly renderPreview?: (onContinue: () => void) => React.ReactElement | null;
33
+ readonly renderGenerating?: (progress: number) => React.ReactElement | null;
34
+ readonly renderResult?: (result: unknown) => React.ReactElement | null;
35
+ }
36
+
37
+ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
38
+ step,
39
+ scenario,
40
+ customData,
41
+ generationProgress,
42
+ generationResult,
43
+ isSaving,
44
+ isSharing,
45
+ onNext,
46
+ onBack,
47
+ onPhotoContinue,
48
+ onDownload,
49
+ onShare,
50
+ onTryAgain,
51
+ t,
52
+ renderPreview,
53
+ renderGenerating,
54
+ renderResult,
55
+ }) => {
56
+ if (!step) {
57
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
58
+ console.warn("[WizardStepRenderer] No current step!");
59
+ }
60
+ return null;
61
+ }
62
+
63
+ switch (step.type) {
64
+ case StepType.SCENARIO_PREVIEW: {
65
+ if (renderPreview) {
66
+ return renderPreview(onNext);
67
+ }
68
+ return (
69
+ <ScenarioPreviewScreen
70
+ scenario={scenario}
71
+ translations={{
72
+ continueButton: t("common.continue"),
73
+ whatToExpect: t("scenarioPreview.whatToExpect"),
74
+ }}
75
+ onContinue={onNext}
76
+ onBack={onBack}
77
+ t={t}
78
+ />
79
+ );
80
+ }
81
+
82
+ case StepType.GENERATING: {
83
+ if (renderGenerating) {
84
+ return renderGenerating(generationProgress);
85
+ }
86
+ return (
87
+ <GeneratingScreen
88
+ progress={generationProgress}
89
+ scenario={scenario}
90
+ t={t}
91
+ />
92
+ );
93
+ }
94
+
95
+ case StepType.RESULT_PREVIEW: {
96
+ if (renderResult) {
97
+ return renderResult(generationResult);
98
+ }
99
+ const creation = generationResult as Creation;
100
+ const imageUrl = creation?.output?.imageUrl || creation?.uri || "";
101
+ if (!imageUrl) return null;
102
+ return (
103
+ <ResultPreviewScreen
104
+ imageUrl={imageUrl}
105
+ isSaving={isSaving}
106
+ isSharing={isSharing}
107
+ onDownload={onDownload}
108
+ onShare={onShare}
109
+ onTryAgain={onTryAgain || onBack}
110
+ onNavigateBack={onTryAgain || onBack}
111
+ translations={{
112
+ title: t("generation.result.title"),
113
+ yourResult: t("generation.result.yourResult"),
114
+ saveButton: t("generation.result.save"),
115
+ saving: t("generation.result.saving"),
116
+ shareButton: t("generation.result.share"),
117
+ sharing: t("generation.result.sharing"),
118
+ tryAnother: t("generation.result.tryAnother"),
119
+ }}
120
+ />
121
+ );
122
+ }
123
+
124
+ case StepType.PARTNER_UPLOAD: {
125
+ const wizardConfig = step.config as WizardStepConfig;
126
+ const titleKey = wizardConfig?.titleKey || `wizard.steps.${step.id}.title`;
127
+ const subtitleKey = wizardConfig?.subtitleKey || `wizard.steps.${step.id}.subtitle`;
128
+ const existingPhoto = customData[step.id] as UploadedImage | undefined;
129
+
130
+ return (
131
+ <GenericPhotoUploadScreen
132
+ translations={{
133
+ title: t(titleKey),
134
+ subtitle: t(subtitleKey),
135
+ continue: t("common.continue"),
136
+ tapToUpload: t("photoUpload.tapToUpload"),
137
+ selectPhoto: t("photoUpload.selectPhoto"),
138
+ change: t("common.change"),
139
+ fileTooLarge: t("common.errors.file_too_large"),
140
+ maxFileSize: t("common.errors.max_file_size"),
141
+ error: t("common.error"),
142
+ uploadFailed: t("common.errors.upload_failed"),
143
+ }}
144
+ t={t}
145
+ onBack={onBack}
146
+ onContinue={(image) => onPhotoContinue(step.id, image)}
147
+ existingImage={existingPhoto}
148
+ />
149
+ );
150
+ }
151
+
152
+ default:
153
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
154
+ console.warn("[WizardStepRenderer] Unhandled step type", { stepType: step.type });
155
+ }
156
+ return null;
157
+ }
158
+ };
@@ -1,2 +1,4 @@
1
1
  export { GenericWizardFlow } from "./GenericWizardFlow";
2
2
  export type { GenericWizardFlowProps } from "./GenericWizardFlow";
3
+ export { WizardStepRenderer } from "./WizardStepRenderer";
4
+ export type { WizardStepRendererProps } from "./WizardStepRenderer";
@@ -0,0 +1 @@
1
+ export { validateScenario, type ScenarioValidationResult } from "./validateScenario";
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Scenario validation utility
3
+ * Validates that scenario has required fields for wizard generation
4
+ */
5
+
6
+ import type { WizardScenarioData } from "../hooks/useWizardGeneration";
7
+
8
+ export interface ScenarioValidationResult {
9
+ isValid: boolean;
10
+ error?: string;
11
+ scenario?: WizardScenarioData;
12
+ }
13
+
14
+ /**
15
+ * Validates scenario data for wizard generation
16
+ * @throws Error if scenario is invalid
17
+ */
18
+ export const validateScenario = (
19
+ scenario: WizardScenarioData | undefined,
20
+ ): WizardScenarioData => {
21
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
22
+ console.log("[validateScenario] Validating", {
23
+ hasScenario: !!scenario,
24
+ scenarioId: scenario?.id,
25
+ hasAiPrompt: !!scenario?.aiPrompt,
26
+ aiPromptLength: scenario?.aiPrompt?.length,
27
+ hasModel: !!scenario?.model,
28
+ outputType: scenario?.outputType,
29
+ });
30
+ }
31
+
32
+ if (!scenario || !scenario.id) {
33
+ throw new Error("[validateScenario] Scenario is required");
34
+ }
35
+
36
+ if (!scenario.aiPrompt || scenario.aiPrompt.trim() === "") {
37
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
38
+ console.error("[validateScenario] CRITICAL: Scenario missing aiPrompt!", {
39
+ scenarioId: scenario.id,
40
+ aiPrompt: scenario.aiPrompt,
41
+ });
42
+ }
43
+ throw new Error(`[validateScenario] Scenario "${scenario.id}" must have aiPrompt field`);
44
+ }
45
+
46
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
47
+ console.log("[validateScenario] Validation passed", {
48
+ scenarioId: scenario.id,
49
+ model: scenario.model,
50
+ outputType: scenario.outputType,
51
+ promptLength: scenario.aiPrompt.length,
52
+ });
53
+ }
54
+
55
+ return scenario;
56
+ };