@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 +1 -1
- package/src/domains/generation/wizard/presentation/components/GenericWizardFlow.tsx +48 -196
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.tsx +158 -0
- package/src/domains/generation/wizard/presentation/components/index.ts +2 -0
- package/src/domains/generation/wizard/presentation/utilities/index.ts +1 -0
- package/src/domains/generation/wizard/presentation/utilities/validateScenario.ts +56 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.26.
|
|
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
|
|
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 {
|
|
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:
|
|
68
|
-
includeGenerating:
|
|
73
|
+
includePreview: true,
|
|
74
|
+
includeGenerating: true,
|
|
69
75
|
});
|
|
70
|
-
}, [featureConfig
|
|
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
|
-
|
|
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
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
133
|
-
throw new Error("[GenericWizardFlow] Scenario is required");
|
|
134
|
-
}
|
|
97
|
+
const validatedScenario = useMemo(() => validateScenario(scenario), [scenario]);
|
|
135
98
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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]
|
|
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
|
-
|
|
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
|
+
};
|
|
@@ -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
|
+
};
|