@umituz/react-native-ai-generation-content 1.26.47 → 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.47",
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",
@@ -12,17 +12,20 @@
12
12
  * NO feature-specific code here - everything driven by configuration!
13
13
  */
14
14
 
15
- import React, { useMemo, useCallback, useEffect, useRef } from "react";
15
+ import React, { useMemo, useCallback, useEffect, useRef, useState } 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";
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";
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";
26
29
 
27
30
  export interface GenericWizardFlowProps {
28
31
  readonly featureConfig: WizardFeatureConfig;
@@ -35,6 +38,7 @@ export interface GenericWizardFlowProps {
35
38
  readonly onGenerationError?: (error: string) => void;
36
39
  readonly onCreditsExhausted?: () => void;
37
40
  readonly onBack?: () => void;
41
+ readonly onTryAgain?: () => void;
38
42
  readonly t: (key: string) => string;
39
43
  readonly translations?: Record<string, string>;
40
44
  readonly renderPreview?: (onContinue: () => void) => React.ReactElement | null;
@@ -53,6 +57,7 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
53
57
  onGenerationError,
54
58
  onCreditsExhausted,
55
59
  onBack,
60
+ onTryAgain,
56
61
  t,
57
62
  translations: _translations,
58
63
  renderPreview,
@@ -60,22 +65,17 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
60
65
  renderResult,
61
66
  }) => {
62
67
  const tokens = useAppDesignTokens();
68
+ const [currentCreation, setCurrentCreation] = useState<Creation | null>(null);
69
+ const prevStepIdRef = useRef<string | undefined>(undefined);
63
70
 
64
- // Build flow steps from wizard config
65
71
  const flowSteps = useMemo<StepDefinition[]>(() => {
66
72
  return buildFlowStepsFromWizard(featureConfig, {
67
- includePreview: !!renderPreview,
68
- includeGenerating: !!renderGenerating,
73
+ includePreview: true,
74
+ includeGenerating: true,
69
75
  });
70
- }, [featureConfig, renderPreview, renderGenerating]);
71
-
72
- // Initialize flow and destructure to prevent infinite loops
73
- const flow = useFlow({
74
- steps: flowSteps,
75
- initialStepIndex: 0,
76
- });
76
+ }, [featureConfig]);
77
77
 
78
- // Destructure flow to get stable references for useCallback dependencies
78
+ const flow = useFlow({ steps: flowSteps, initialStepIndex: 0 });
79
79
  const {
80
80
  currentStep,
81
81
  currentStepIndex,
@@ -89,76 +89,27 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
89
89
  setResult,
90
90
  } = flow;
91
91
 
92
- // Handle progress change - memoized to prevent infinite loops
93
- const handleProgressChange = useCallback(
94
- (progress: number) => {
95
- updateProgress(progress);
96
- },
97
- [updateProgress],
98
- );
99
-
100
- // Handle generation complete - saves result and advances to result preview
101
- const handleGenerationComplete = useCallback(
102
- (result: unknown) => {
103
- if (typeof __DEV__ !== "undefined" && __DEV__) {
104
- console.log("[GenericWizardFlow] Generation completed, saving result and advancing to result preview");
105
- }
106
- // Save result in flow state
107
- setResult(result);
108
- // Advance to result preview step
109
- nextStep();
110
- // Notify parent
111
- onGenerationComplete?.(result);
112
- },
113
- [setResult, nextStep, onGenerationComplete],
114
- );
115
-
116
- // Validate scenario - NO FALLBACK, aiPrompt is REQUIRED
117
- const validatedScenario = useMemo(() => {
118
- if (typeof __DEV__ !== "undefined" && __DEV__) {
119
- console.log("[GenericWizardFlow] Validating scenario", {
120
- hasScenario: !!scenario,
121
- scenarioId: scenario?.id,
122
- hasAiPrompt: !!scenario?.aiPrompt,
123
- aiPromptValue: scenario?.aiPrompt,
124
- aiPromptLength: scenario?.aiPrompt?.length,
125
- hasModel: !!scenario?.model,
126
- scenarioModel: scenario?.model,
127
- outputType: scenario?.outputType,
128
- fullScenario: JSON.stringify(scenario, null, 2),
129
- });
130
- }
92
+ const resultImageUrl = currentCreation?.output?.imageUrl || currentCreation?.uri || "";
93
+ const { isSaving, isSharing, handleDownload, handleShare } = useResultActions({
94
+ imageUrl: resultImageUrl,
95
+ });
131
96
 
132
- if (!scenario || !scenario.id) {
133
- throw new Error("[GenericWizardFlow] Scenario is required");
134
- }
97
+ const validatedScenario = useMemo(() => validateScenario(scenario), [scenario]);
135
98
 
136
- if (!scenario.aiPrompt || scenario.aiPrompt.trim() === "") {
137
- if (typeof __DEV__ !== "undefined" && __DEV__) {
138
- console.error("[GenericWizardFlow] CRITICAL: Scenario missing aiPrompt!", {
139
- scenarioId: scenario.id,
140
- aiPrompt: scenario.aiPrompt,
141
- fullScenario: scenario,
142
- });
143
- }
144
- throw new Error(`[GenericWizardFlow] Scenario "${scenario.id}" must have aiPrompt field`);
145
- }
99
+ const handleProgressChange = useCallback((progress: number) => {
100
+ updateProgress(progress);
101
+ }, [updateProgress]);
146
102
 
103
+ const handleGenerationComplete = useCallback((result: unknown) => {
147
104
  if (typeof __DEV__ !== "undefined" && __DEV__) {
148
- console.log("[GenericWizardFlow] Scenario validation passed", {
149
- scenarioId: scenario.id,
150
- model: scenario.model,
151
- outputType: scenario.outputType,
152
- promptLength: scenario.aiPrompt.length,
153
- promptPreview: scenario.aiPrompt.substring(0, 100),
154
- });
105
+ console.log("[GenericWizardFlow] Generation completed");
155
106
  }
107
+ setResult(result);
108
+ setCurrentCreation(result as Creation);
109
+ nextStep();
110
+ onGenerationComplete?.(result);
111
+ }, [setResult, nextStep, onGenerationComplete]);
156
112
 
157
- return scenario;
158
- }, [scenario]);
159
-
160
- // Generation hook - handles AI generation automatically
161
- // Note: Hook is used for its side effects (automatic generation)
162
113
  useWizardGeneration({
163
114
  scenario: validatedScenario,
164
115
  wizardData: customData,
@@ -171,40 +122,16 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
171
122
  onCreditsExhausted,
172
123
  });
173
124
 
174
- // Track previous step ID to prevent infinite loops
175
- const prevStepIdRef = useRef<string | undefined>(undefined);
176
-
177
- // DEBUG logging
178
- if (typeof __DEV__ !== "undefined" && __DEV__) {
179
- console.log("[GenericWizardFlow] Render", {
180
- featureId: featureConfig.id,
181
- currentStepId: currentStep?.id,
182
- currentStepType: currentStep?.type,
183
- stepIndex: currentStepIndex,
184
- totalSteps: flowSteps.length,
185
- });
186
- }
187
-
188
- // Notify parent when step changes
189
- // Only call onStepChange when step ID actually changes (not on every object reference change)
190
125
  useEffect(() => {
191
126
  if (currentStep && onStepChange) {
192
127
  const currentStepId = currentStep.id;
193
- // Only notify if step ID changed
194
128
  if (prevStepIdRef.current !== currentStepId) {
195
129
  prevStepIdRef.current = currentStepId;
196
- if (typeof __DEV__ !== "undefined" && __DEV__) {
197
- console.log("[GenericWizardFlow] Step changed", {
198
- stepId: currentStep.id,
199
- stepType: currentStep.type,
200
- });
201
- }
202
130
  onStepChange(currentStep.id, currentStep.type);
203
131
  }
204
132
  }
205
133
  }, [currentStep, currentStepIndex, onStepChange]);
206
134
 
207
- // Handle back
208
135
  const handleBack = useCallback(() => {
209
136
  if (currentStepIndex === 0) {
210
137
  onBack?.();
@@ -213,18 +140,12 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
213
140
  }
214
141
  }, [currentStepIndex, previousStep, onBack]);
215
142
 
216
- // Handle photo continue - saves photo and moves to next step
217
143
  const handlePhotoContinue = useCallback((stepId: string, image: UploadedImage) => {
218
144
  setCustomData(stepId, image);
219
145
 
220
- // Check if this is the last step before generating
221
146
  if (currentStepIndex === flowSteps.length - 2) {
222
- // Next step is GENERATING - call onGenerationStart
223
147
  if (onGenerationStart) {
224
148
  onGenerationStart({ ...customData, [stepId]: image }, () => {
225
- if (typeof __DEV__ !== "undefined" && __DEV__) {
226
- console.log("[GenericWizardFlow] Proceeding to GENERATING step");
227
- }
228
149
  nextStep();
229
150
  });
230
151
  }
@@ -234,96 +155,27 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
234
155
  nextStep();
235
156
  }, [currentStepIndex, flowSteps.length, customData, setCustomData, nextStep, onGenerationStart]);
236
157
 
237
- // Render current step
238
- const renderCurrentStep = useCallback(() => {
239
- const step = currentStep;
240
- if (!step) {
241
- if (typeof __DEV__ !== "undefined" && __DEV__) {
242
- console.warn("[GenericWizardFlow] No current step!");
243
- }
244
- return null;
245
- }
246
-
247
- if (typeof __DEV__ !== "undefined" && __DEV__) {
248
- console.log("[GenericWizardFlow] Rendering step", {
249
- stepId: step.id,
250
- stepType: step.type,
251
- });
252
- }
253
-
254
- // Special steps with custom renderers
255
- switch (step.type) {
256
- case StepType.SCENARIO_PREVIEW:
257
- // Preview continues to next step automatically
258
- return renderPreview?.(nextStep) || null;
259
-
260
- case StepType.GENERATING:
261
- return renderGenerating?.(generationProgress) || null;
262
-
263
- case StepType.RESULT_PREVIEW:
264
- return renderResult?.(generationResult) || null;
265
-
266
- case StepType.PARTNER_UPLOAD: {
267
- // Get wizard step config
268
- const wizardConfig = step.config as WizardStepConfig;
269
-
270
- // Use titleKey from config, fallback to step-specific translation key
271
- const titleKey = wizardConfig?.titleKey || `wizard.steps.${step.id}.title`;
272
- const title = t(titleKey);
273
-
274
- // Subtitle from config
275
- const subtitleKey = wizardConfig?.subtitleKey || `wizard.steps.${step.id}.subtitle`;
276
- const subtitle = t(subtitleKey);
277
-
278
- // Get existing photo for this step from customData
279
- const existingPhoto = customData[step.id] as UploadedImage | undefined;
280
-
281
- return (
282
- <GenericPhotoUploadScreen
283
- translations={{
284
- title,
285
- subtitle,
286
- continue: t("common.continue"),
287
- tapToUpload: t("photoUpload.tapToUpload"),
288
- selectPhoto: t("photoUpload.selectPhoto"),
289
- change: t("common.change"),
290
- fileTooLarge: t("common.errors.file_too_large"),
291
- maxFileSize: t("common.errors.max_file_size"),
292
- error: t("common.error"),
293
- uploadFailed: t("common.errors.upload_failed"),
294
- }}
295
- t={t}
296
- onBack={handleBack}
297
- onContinue={(image) => handlePhotoContinue(step.id, image)}
298
- existingImage={existingPhoto}
299
- />
300
- );
301
- }
302
-
303
- default:
304
- // Other step types should be handled by custom render props
305
- if (typeof __DEV__ !== "undefined" && __DEV__) {
306
- console.warn("[GenericWizardFlow] Unhandled step type", { stepType: step.type });
307
- }
308
- return null;
309
- }
310
- }, [
311
- currentStep,
312
- customData,
313
- generationProgress,
314
- generationResult,
315
- nextStep,
316
- renderPreview,
317
- renderGenerating,
318
- renderResult,
319
- handlePhotoContinue,
320
- handleBack,
321
- t,
322
- ]);
323
-
324
158
  return (
325
159
  <View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
326
- {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
+ />
327
179
  </View>
328
180
  );
329
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
+ };