@umituz/react-native-ai-generation-content 1.26.40 โ†’ 1.26.42

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.40",
3
+ "version": "1.26.42",
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",
@@ -21,8 +21,8 @@ import type { WizardFeatureConfig, WizardStepConfig } from "../../domain/entitie
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
- import { PhotoStep } from "../../../../../presentation/components/photo-step/PhotoStep";
25
- import { usePhotoUploadState } from "../hooks/usePhotoUploadState";
24
+ import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
25
+ import { GenericPhotoUploadScreen } from "../screens/GenericPhotoUploadScreen";
26
26
 
27
27
  export interface GenericWizardFlowProps {
28
28
  readonly featureConfig: WizardFeatureConfig;
@@ -54,7 +54,7 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
54
54
  onCreditsExhausted,
55
55
  onBack,
56
56
  t,
57
- translations,
57
+ translations: _translations,
58
58
  renderPreview,
59
59
  renderGenerating,
60
60
  renderResult,
@@ -187,44 +187,6 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
187
187
  }
188
188
  }, [currentStep, currentStepIndex, onStepChange]);
189
189
 
190
- // Handle step continue
191
- const handleStepContinue = useCallback(
192
- (stepData: Record<string, unknown>) => {
193
- if (typeof __DEV__ !== "undefined" && __DEV__) {
194
- console.log("[GenericWizardFlow] Step continue", {
195
- stepId: currentStep?.id,
196
- data: stepData,
197
- });
198
- }
199
-
200
- // Store step data in custom data
201
- Object.entries(stepData).forEach(([key, value]) => {
202
- setCustomData(key, value);
203
- });
204
-
205
- // Check if this is the last step before generating
206
- if (currentStepIndex === flowSteps.length - 2) {
207
- // Next step is GENERATING
208
- // Notify parent and provide callback to proceed to generating
209
- // Parent will call proceedToGenerating() after feature gate passes
210
- if (onGenerationStart) {
211
- onGenerationStart(customData, () => {
212
- if (typeof __DEV__ !== "undefined" && __DEV__) {
213
- console.log("[GenericWizardFlow] Proceeding to GENERATING step");
214
- }
215
- nextStep();
216
- });
217
- }
218
- // DON'T call nextStep() here - parent will call it via proceedToGenerating callback
219
- return;
220
- }
221
-
222
- // Move to next step (for all non-generation steps)
223
- nextStep();
224
- },
225
- [currentStep, currentStepIndex, customData, setCustomData, nextStep, flowSteps.length, onGenerationStart],
226
- );
227
-
228
190
  // Handle back
229
191
  const handleBack = useCallback(() => {
230
192
  if (currentStepIndex === 0) {
@@ -234,24 +196,26 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
234
196
  }
235
197
  }, [currentStepIndex, previousStep, onBack]);
236
198
 
237
- // Photo upload state translations (generic, used for all photo upload steps)
238
- const photoUploadTranslations = useMemo(() => ({
239
- fileTooLarge: t("common.errors.file_too_large"),
240
- maxFileSize: t("common.errors.max_file_size"),
241
- error: t("common.error"),
242
- uploadFailed: t("common.errors.upload_failed"),
243
- }), [t]);
244
-
245
- const photoUploadHook = usePhotoUploadState({
246
- translations: photoUploadTranslations,
247
- });
248
-
249
- // Save photo when uploaded
250
- useEffect(() => {
251
- if (photoUploadHook.image && currentStep) {
252
- setCustomData(currentStep.id, photoUploadHook.image);
199
+ // Handle photo continue - saves photo and moves to next step
200
+ const handlePhotoContinue = useCallback((stepId: string, image: UploadedImage) => {
201
+ setCustomData(stepId, image);
202
+
203
+ // Check if this is the last step before generating
204
+ if (currentStepIndex === flowSteps.length - 2) {
205
+ // Next step is GENERATING - call onGenerationStart
206
+ if (onGenerationStart) {
207
+ onGenerationStart({ ...customData, [stepId]: image }, () => {
208
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
209
+ console.log("[GenericWizardFlow] Proceeding to GENERATING step");
210
+ }
211
+ nextStep();
212
+ });
213
+ }
214
+ return;
253
215
  }
254
- }, [photoUploadHook.image, currentStep, setCustomData]);
216
+
217
+ nextStep();
218
+ }, [currentStepIndex, flowSteps.length, customData, setCustomData, nextStep, onGenerationStart]);
255
219
 
256
220
  // Render current step
257
221
  const renderCurrentStep = useCallback(() => {
@@ -290,37 +254,31 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
290
254
  const titleKey = wizardConfig?.titleKey || `wizard.steps.${step.id}.title`;
291
255
  const title = t(titleKey);
292
256
 
293
- // Subtitle is optional
294
- const subtitle = wizardConfig?.subtitleKey ? t(wizardConfig.subtitleKey) : undefined;
257
+ // Subtitle from config
258
+ const subtitleKey = wizardConfig?.subtitleKey || `wizard.steps.${step.id}.subtitle`;
259
+ const subtitle = t(subtitleKey);
295
260
 
296
261
  // Get existing photo for this step from customData
297
- const existingPhoto = customData[step.id];
298
- const imageUri = existingPhoto && typeof existingPhoto === "object" && "uri" in existingPhoto
299
- ? (existingPhoto.uri as string)
300
- : photoUploadHook.image?.uri || null;
262
+ const existingPhoto = customData[step.id] as UploadedImage | undefined;
301
263
 
302
264
  return (
303
- <PhotoStep
304
- config={{
305
- enabled: true,
306
- order: currentStepIndex,
307
- id: step.id,
308
- header: {},
309
- photoCard: {},
310
- enableValidation: false,
311
- }}
312
- imageUri={imageUri}
313
- isValidating={false}
314
- isValid={null}
315
- onPhotoSelect={photoUploadHook.handlePickImage}
316
- disabled={false}
317
- title={title}
318
- subtitle={subtitle}
265
+ <GenericPhotoUploadScreen
319
266
  translations={{
267
+ title,
268
+ subtitle,
269
+ continue: t("common.continue"),
320
270
  tapToUpload: t("photoUpload.tapToUpload"),
321
271
  selectPhoto: t("photoUpload.selectPhoto"),
322
272
  change: t("common.change"),
273
+ fileTooLarge: t("common.errors.file_too_large"),
274
+ maxFileSize: t("common.errors.max_file_size"),
275
+ error: t("common.error"),
276
+ uploadFailed: t("common.errors.upload_failed"),
323
277
  }}
278
+ t={t}
279
+ onBack={handleBack}
280
+ onContinue={(image) => handlePhotoContinue(step.id, image)}
281
+ existingImage={existingPhoto}
324
282
  />
325
283
  );
326
284
  }
@@ -334,16 +292,16 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
334
292
  }
335
293
  }, [
336
294
  currentStep,
295
+ customData,
337
296
  generationProgress,
338
297
  generationResult,
339
298
  nextStep,
340
299
  renderPreview,
341
300
  renderGenerating,
342
301
  renderResult,
343
- handleStepContinue,
302
+ handlePhotoContinue,
344
303
  handleBack,
345
304
  t,
346
- translations,
347
305
  ]);
348
306
 
349
307
  return (
@@ -23,6 +23,7 @@ export interface PhotoUploadTranslations {
23
23
  export interface UsePhotoUploadStateProps {
24
24
  readonly config?: PhotoUploadConfig;
25
25
  readonly translations: PhotoUploadTranslations;
26
+ readonly initialImage?: UploadedImage;
26
27
  }
27
28
 
28
29
  export interface UsePhotoUploadStateReturn {
@@ -36,8 +37,9 @@ const DEFAULT_MAX_FILE_SIZE_MB = 10;
36
37
  export const usePhotoUploadState = ({
37
38
  config,
38
39
  translations,
40
+ initialImage,
39
41
  }: UsePhotoUploadStateProps): UsePhotoUploadStateReturn => {
40
- const [image, setImage] = useState<UploadedImage | null>(null);
42
+ const [image, setImage] = useState<UploadedImage | null>(initialImage || null);
41
43
 
42
44
  const maxFileSizeMB = config?.maxFileSizeMB ?? DEFAULT_MAX_FILE_SIZE_MB;
43
45
 
@@ -0,0 +1,229 @@
1
+ /**
2
+ * Generic Photo Upload Screen
3
+ * Used by wizard domain for ANY photo upload step
4
+ * NO feature-specific concepts (no partner, couple, etc.)
5
+ * Works for: couple features, face-swap, image-to-video, ANY photo upload need
6
+ */
7
+
8
+ import React, { useMemo } from "react";
9
+ import { View, TouchableOpacity, StyleSheet } from "react-native";
10
+ import {
11
+ useAppDesignTokens,
12
+ ScreenLayout,
13
+ AtomicText,
14
+ AtomicIcon,
15
+ NavigationHeader,
16
+ InfoGrid,
17
+ type DesignTokens,
18
+ type InfoGridItem,
19
+ } from "@umituz/react-native-design-system";
20
+ import { PhotoUploadCard } from "../../../../../presentation/components";
21
+ import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
22
+ import { usePhotoUploadState } from "../hooks/usePhotoUploadState";
23
+
24
+ export interface PhotoUploadScreenTranslations {
25
+ readonly title: string;
26
+ readonly subtitle: string;
27
+ readonly continue: string;
28
+ readonly tapToUpload: string;
29
+ readonly selectPhoto: string;
30
+ readonly change: string;
31
+ readonly analyzing?: string;
32
+ readonly fileTooLarge: string;
33
+ readonly maxFileSize: string;
34
+ readonly error: string;
35
+ readonly uploadFailed: string;
36
+ readonly aiDisclosure?: string;
37
+ }
38
+
39
+ export interface PhotoUploadScreenConfig {
40
+ readonly showPhotoTips?: boolean;
41
+ readonly maxFileSizeMB?: number;
42
+ }
43
+
44
+ export interface PhotoUploadScreenProps {
45
+ readonly translations: PhotoUploadScreenTranslations;
46
+ readonly t: (key: string) => string;
47
+ readonly config?: PhotoUploadScreenConfig;
48
+ readonly onBack: () => void;
49
+ readonly onContinue: (image: UploadedImage) => void;
50
+ readonly existingImage?: UploadedImage | null;
51
+ }
52
+
53
+ const DEFAULT_CONFIG: PhotoUploadScreenConfig = {
54
+ showPhotoTips: true,
55
+ maxFileSizeMB: 10,
56
+ };
57
+
58
+ export const GenericPhotoUploadScreen: React.FC<PhotoUploadScreenProps> = ({
59
+ translations,
60
+ t,
61
+ config = DEFAULT_CONFIG,
62
+ onBack,
63
+ onContinue,
64
+ existingImage,
65
+ }) => {
66
+ const tokens = useAppDesignTokens();
67
+
68
+ const { image, handlePickImage, canContinue } = usePhotoUploadState({
69
+ config: { maxFileSizeMB: config.maxFileSizeMB },
70
+ translations: {
71
+ fileTooLarge: translations.fileTooLarge,
72
+ maxFileSize: translations.maxFileSize,
73
+ error: translations.error,
74
+ uploadFailed: translations.uploadFailed,
75
+ },
76
+ initialImage: existingImage || undefined,
77
+ });
78
+
79
+ const handleContinuePress = () => {
80
+ if (!canContinue || !image) return;
81
+ onContinue(image);
82
+ };
83
+
84
+ const styles = useMemo(() => createStyles(tokens), [tokens]);
85
+ const showPhotoTips = config.showPhotoTips ?? true;
86
+
87
+ // Build photo tips items from translations
88
+ const photoTipsItems: InfoGridItem[] = useMemo(() => {
89
+ const tipKeys = [
90
+ { key: "photoUpload.tips.clearFace", icon: "Smile" },
91
+ { key: "photoUpload.tips.goodLighting", icon: "Sun" },
92
+ { key: "photoUpload.tips.recentPhoto", icon: "Clock" },
93
+ { key: "photoUpload.tips.noFilters", icon: "Image" },
94
+ ];
95
+ return tipKeys.map(({ key, icon }) => ({
96
+ text: t(key),
97
+ icon,
98
+ }));
99
+ }, [t]);
100
+
101
+ return (
102
+ <View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
103
+ <NavigationHeader
104
+ title={translations.title}
105
+ onBackPress={onBack}
106
+ rightElement={
107
+ <TouchableOpacity
108
+ onPress={handleContinuePress}
109
+ activeOpacity={0.7}
110
+ disabled={!canContinue || !image}
111
+ style={[
112
+ styles.continueButton,
113
+ {
114
+ backgroundColor: canContinue && image ? tokens.colors.primary : tokens.colors.surfaceVariant,
115
+ opacity: canContinue && image ? 1 : 0.5,
116
+ },
117
+ ]}
118
+ >
119
+ <AtomicText
120
+ type="bodyMedium"
121
+ style={[
122
+ styles.continueText,
123
+ { color: canContinue && image ? tokens.colors.onPrimary : tokens.colors.textSecondary },
124
+ ]}
125
+ >
126
+ {translations.continue}
127
+ </AtomicText>
128
+ <AtomicIcon
129
+ name="ChevronRight"
130
+ size="sm"
131
+ color={canContinue && image ? "onPrimary" : "textSecondary"}
132
+ />
133
+ </TouchableOpacity>
134
+ }
135
+ />
136
+ <ScreenLayout
137
+ edges={["left", "right"]}
138
+ backgroundColor="transparent"
139
+ scrollable={true}
140
+ keyboardAvoiding={true}
141
+ contentContainerStyle={styles.scrollContent}
142
+ hideScrollIndicator={true}
143
+ >
144
+ <AtomicText style={[styles.subtitle, { color: tokens.colors.textSecondary }]}>
145
+ {translations.subtitle}
146
+ </AtomicText>
147
+
148
+ {/* Photo Tips - InfoGrid version */}
149
+ {showPhotoTips && (
150
+ <View style={styles.tipsContainer}>
151
+ <InfoGrid
152
+ items={photoTipsItems}
153
+ columns={2}
154
+ title={t("photoUpload.tips.title")}
155
+ headerIcon="Lightbulb"
156
+ />
157
+ </View>
158
+ )}
159
+
160
+ <PhotoUploadCard
161
+ imageUri={image?.previewUrl || image?.uri || null}
162
+ onPress={handlePickImage}
163
+ isValidating={false}
164
+ isValid={null}
165
+ translations={{
166
+ tapToUpload: translations.tapToUpload,
167
+ selectPhoto: translations.selectPhoto,
168
+ change: translations.change,
169
+ analyzing: translations.analyzing,
170
+ }}
171
+ />
172
+
173
+ {translations.aiDisclosure && (
174
+ <View style={styles.disclosureContainer}>
175
+ <AtomicText
176
+ type="labelSmall"
177
+ style={[styles.disclosureText, { color: tokens.colors.textSecondary }]}
178
+ >
179
+ {translations.aiDisclosure}
180
+ </AtomicText>
181
+ </View>
182
+ )}
183
+ </ScreenLayout>
184
+ </View>
185
+ );
186
+ };
187
+
188
+ const createStyles = (tokens: DesignTokens) =>
189
+ StyleSheet.create({
190
+ container: {
191
+ flex: 1,
192
+ },
193
+ scrollContent: {
194
+ paddingBottom: 40,
195
+ },
196
+ subtitle: {
197
+ fontSize: 16,
198
+ textAlign: "center",
199
+ marginHorizontal: 24,
200
+ marginBottom: 24,
201
+ },
202
+ tipsContainer: {
203
+ marginHorizontal: 24,
204
+ marginBottom: 20,
205
+ },
206
+ continueButton: {
207
+ flexDirection: "row",
208
+ alignItems: "center",
209
+ paddingHorizontal: tokens.spacing.md,
210
+ paddingVertical: tokens.spacing.xs,
211
+ borderRadius: tokens.borders.radius.full,
212
+ },
213
+ continueText: {
214
+ fontWeight: "800",
215
+ marginRight: 4,
216
+ },
217
+ disclosureContainer: {
218
+ marginTop: 24,
219
+ marginHorizontal: 24,
220
+ padding: 16,
221
+ borderRadius: 12,
222
+ backgroundColor: tokens.colors.surfaceVariant + "40",
223
+ },
224
+ disclosureText: {
225
+ textAlign: "center",
226
+ lineHeight: 18,
227
+ opacity: 0.8,
228
+ },
229
+ });
@@ -1 +1,7 @@
1
1
  export { GeneratingScreen } from "./GeneratingScreen";
2
+ export { GenericPhotoUploadScreen } from "./GenericPhotoUploadScreen";
3
+ export type {
4
+ PhotoUploadScreenTranslations,
5
+ PhotoUploadScreenConfig,
6
+ PhotoUploadScreenProps,
7
+ } from "./GenericPhotoUploadScreen";
@@ -261,6 +261,7 @@ export enum ScenarioId {
261
261
  NEW_YEARS = "new_years",
262
262
  VALENTINES = "valentines",
263
263
  BIRTHDAY = "birthday",
264
+ HALLOWEEN_DUO = "halloween_duo",
264
265
 
265
266
  // Home Life
266
267
  MORNING_COFFEE = "morning_coffee",
@@ -58,7 +58,7 @@ export const CELEBRATION_SCENARIOS: Omit<Scenario, 'outputType' | 'category'>[]
58
58
  ),
59
59
  },
60
60
  {
61
- id: ScenarioId.HALLOWEEN,
61
+ id: ScenarioId.HALLOWEEN_DUO,
62
62
  title: "Halloween Duo",
63
63
  description: "Spooky and sweet",
64
64
  icon: "๐ŸŽƒ",
@@ -92,7 +92,7 @@ export const CONNECTION_SCENARIOS: Omit<Scenario, 'outputType' | 'category'>[] =
92
92
  description: "Love that deepens with time",
93
93
  icon: "๐ŸŒณ",
94
94
  imageUrl:
95
- "https://images.unsplash.com/photo-1503454537195-1dcabb73ffb9?w=800",
95
+ "https://images.unsplash.com/photo-1447005497901-b3e9ee359928?w=800",
96
96
  aiPrompt:
97
97
  "A couple in their older years but with the same spark in their eyes, looking at a photo album of their younger selves, looking at the camera with wise and deeply happy smiles, warm sun-drenched living room with mementos, celebrating a lifetime of growth",
98
98
  storyTemplate: createStoryTemplate(
@@ -8,7 +8,7 @@ export const DAILY_ESSENCE_SCENARIOS: Omit<Scenario, 'outputType' | 'category'>[
8
8
  description: "Moving in together",
9
9
  icon: "๐Ÿ“ฆ",
10
10
  imageUrl:
11
- "https://images.unsplash.com/photo-1583214532529-65d194759083?w=800",
11
+ "https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=800",
12
12
  aiPrompt:
13
13
  "A couple in an empty living room surrounded by cardboard boxes, both looking at the camera with exhausted but happy smiles, eating pizza directly from the box on the floor, messy hair, sunlight streaming through bare windows, sense of new beginnings and shared excitement",
14
14
  storyTemplate: createStoryTemplate(
@@ -22,7 +22,7 @@ export const DAILY_ESSENCE_SCENARIOS: Omit<Scenario, 'outputType' | 'category'>[
22
22
  description: "Fun in the mundane",
23
23
  icon: "๐Ÿงบ",
24
24
  imageUrl:
25
- "https://images.unsplash.com/photo-1517677208171-0bc6725a3e60?w=800",
25
+ "https://images.unsplash.com/photo-1545173168-9f1947eebb7f?w=800",
26
26
  aiPrompt:
27
27
  "A couple doing laundry together in a bright laundry room, both looking at the camera with playful laughs, throwing a clean sock at each other, piles of colorful clothes around, warm domestic atmosphere, authentic joy in a daily chore",
28
28
  storyTemplate: createStoryTemplate(
@@ -78,7 +78,7 @@ export const ICONIC_MOMENTS_SCENARIOS: Omit<Scenario, 'outputType' | 'category'>
78
78
  description: "High-flying duo",
79
79
  icon: "๐Ÿ—๏ธ",
80
80
  imageUrl:
81
- "https://images.unsplash.com/photo-1449156003946-3197ae0107cb?w=800",
81
+ "https://images.unsplash.com/photo-1480714378408-67cf0d13bc1b?w=800",
82
82
  aiPrompt:
83
83
  "A couple sitting together on a steel beam high above a city skyline, recreating the iconic 1932 photo, both looking at camera with relaxed daring smiles, legs dangling over the edge, misty city and skyscrapers in background, sepia-toned vintage photography, adventurous and brave",
84
84
  storyTemplate: createStoryTemplate(
@@ -63,7 +63,7 @@ export const MUSIC_SCENARIOS: Omit<Scenario, 'outputType' | 'category'>[] = [
63
63
  description: "Soulful rhythm",
64
64
  icon: "๐ŸŽท",
65
65
  imageUrl:
66
- "https://images.unsplash.com/photo-1415202354747-405cbdec5de7?w=800",
66
+ "https://images.unsplash.com/photo-1511192336575-5a79af67a629?w=800",
67
67
  aiPrompt:
68
68
  "A couple in a smoky dark jazz club, man playing a saxophone, woman leaning against a piano looking at camera with a soulful expression, wearing elegant evening attire, blue and amber spotlight lighting, intimate and moody",
69
69
  storyTemplate: createStoryTemplate(
@@ -8,7 +8,7 @@ export const STEAMPUNK_SCENARIOS: Omit<Scenario, 'outputType' | 'category'>[] =
8
8
  description: "Commanders of the clouds",
9
9
  icon: "๐ŸŽˆ",
10
10
  imageUrl:
11
- "https://images.unsplash.com/photo-1449156003946-3197ae0107cb?w=800",
11
+ "https://images.unsplash.com/photo-1534447677768-be436bb09401?w=800",
12
12
  aiPrompt:
13
13
  "A couple as captains on the bridge of a massive brass-plated airship, looking at the camera with confident adventurous smiles, wearing leather aviator coats and intricate brass goggles, steering wheels and glowing pressure gauges in background, clouds and sunset outside the windows, cinematic steampunk aesthetic",
14
14
  storyTemplate: createStoryTemplate(
@@ -36,7 +36,7 @@ export const STOLEN_MOMENTS_SCENARIOS: Omit<Scenario, 'outputType' | 'category'>
36
36
  description: "Close enough to touch",
37
37
  icon: "๐Ÿ›—",
38
38
  imageUrl:
39
- "https://images.unsplash.com/photo-1449156003946-3197ae0107cb?w=800",
39
+ "https://images.unsplash.com/photo-1527684651001-731c474bbb5a?w=800",
40
40
  aiPrompt:
41
41
  "A couple standing very close in a modern glass elevator, man behind woman leaning in towards her neck, woman looking at the camera through the mirror reflection with a breathless expression, silver metal surfaces and digital floor numbers in background, high-tension proximity",
42
42
  storyTemplate: createStoryTemplate(
@@ -36,7 +36,7 @@ export const SURREAL_DREAMS_SCENARIOS: Omit<Scenario, 'outputType' | 'category'>
36
36
  description: "Symmetry of love",
37
37
  icon: "๐Ÿชž",
38
38
  imageUrl:
39
- "https://images.unsplash.com/photo-1481349518771-2dc0feed76ad?w=800",
39
+ "https://images.unsplash.com/photo-1518837695005-2083093ee35b?w=800",
40
40
  aiPrompt:
41
41
  "A couple standing on a perfectly reflective black crystal floor, surrounded by giant floating mirrors that show different versions of their future together, looking at their reflections with profound smiles, dark void environment with glowing crystalline structures, highly artistic and surreal",
42
42
  storyTemplate: createStoryTemplate(