@umituz/react-native-ai-generation-content 1.17.266 → 1.17.268
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/result-preview/presentation/components/GenerationErrorScreen.tsx +196 -0
- package/src/domains/result-preview/presentation/components/index.ts +6 -0
- package/src/features/couple-future/index.ts +19 -0
- package/src/features/couple-future/infrastructure/generationUtils.ts +228 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.268",
|
|
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",
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GenerationErrorScreen
|
|
3
|
+
* Generic error screen for AI generation failures
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useMemo } from "react";
|
|
7
|
+
import { View, StyleSheet, StyleProp, ViewStyle } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
ScreenLayout,
|
|
10
|
+
AtomicText,
|
|
11
|
+
AtomicIcon,
|
|
12
|
+
useAppDesignTokens,
|
|
13
|
+
AtomicButton,
|
|
14
|
+
type DesignTokens,
|
|
15
|
+
} from "@umituz/react-native-design-system";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Error screen translations
|
|
19
|
+
*/
|
|
20
|
+
export interface GenerationErrorTranslations {
|
|
21
|
+
/** Error title */
|
|
22
|
+
readonly title: string;
|
|
23
|
+
/** Try again button */
|
|
24
|
+
readonly tryAgain: string;
|
|
25
|
+
/** Choose another button */
|
|
26
|
+
readonly chooseAnother: string;
|
|
27
|
+
/** Credit info message */
|
|
28
|
+
readonly noCreditCharged: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Error screen config
|
|
33
|
+
*/
|
|
34
|
+
export interface GenerationErrorConfig {
|
|
35
|
+
/** Show credit info message */
|
|
36
|
+
readonly showCreditInfo?: boolean;
|
|
37
|
+
/** Icon name */
|
|
38
|
+
readonly iconName?: string;
|
|
39
|
+
/** Icon size */
|
|
40
|
+
readonly iconSize?: number;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Error screen props
|
|
45
|
+
*/
|
|
46
|
+
export interface GenerationErrorScreenProps {
|
|
47
|
+
/** Error message to display */
|
|
48
|
+
readonly errorMessage?: string;
|
|
49
|
+
/** Translations */
|
|
50
|
+
readonly translations: GenerationErrorTranslations;
|
|
51
|
+
/** Configuration */
|
|
52
|
+
readonly config?: GenerationErrorConfig;
|
|
53
|
+
/** Try again callback */
|
|
54
|
+
readonly onTryAgain: () => void;
|
|
55
|
+
/** Choose another scenario callback */
|
|
56
|
+
readonly onChooseAnother: () => void;
|
|
57
|
+
/** Optional custom style */
|
|
58
|
+
readonly style?: StyleProp<ViewStyle>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const DEFAULT_CONFIG: GenerationErrorConfig = {
|
|
62
|
+
showCreditInfo: true,
|
|
63
|
+
iconName: "alert-circle",
|
|
64
|
+
iconSize: 56,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const GenerationErrorScreen: React.FC<GenerationErrorScreenProps> = ({
|
|
68
|
+
errorMessage,
|
|
69
|
+
translations,
|
|
70
|
+
config = DEFAULT_CONFIG,
|
|
71
|
+
onTryAgain,
|
|
72
|
+
onChooseAnother,
|
|
73
|
+
style,
|
|
74
|
+
}) => {
|
|
75
|
+
const tokens = useAppDesignTokens();
|
|
76
|
+
const styles = useMemo(() => createStyles(tokens), [tokens]);
|
|
77
|
+
const mergedConfig = { ...DEFAULT_CONFIG, ...config };
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<View
|
|
81
|
+
style={[
|
|
82
|
+
styles.container,
|
|
83
|
+
{ backgroundColor: tokens.colors.backgroundPrimary },
|
|
84
|
+
style,
|
|
85
|
+
]}
|
|
86
|
+
>
|
|
87
|
+
<ScreenLayout
|
|
88
|
+
scrollable={false}
|
|
89
|
+
edges={["top", "bottom"]}
|
|
90
|
+
backgroundColor="transparent"
|
|
91
|
+
>
|
|
92
|
+
<View style={styles.content}>
|
|
93
|
+
<View style={styles.iconContainer}>
|
|
94
|
+
<AtomicIcon
|
|
95
|
+
name={mergedConfig.iconName || "alert-circle"}
|
|
96
|
+
size={mergedConfig.iconSize || 56}
|
|
97
|
+
customColor={tokens.colors.error}
|
|
98
|
+
/>
|
|
99
|
+
</View>
|
|
100
|
+
|
|
101
|
+
<AtomicText style={styles.title}>{translations.title}</AtomicText>
|
|
102
|
+
|
|
103
|
+
<AtomicText style={styles.message}>
|
|
104
|
+
{errorMessage || translations.title}
|
|
105
|
+
</AtomicText>
|
|
106
|
+
|
|
107
|
+
{mergedConfig.showCreditInfo && (
|
|
108
|
+
<View style={styles.infoContainer}>
|
|
109
|
+
<AtomicIcon
|
|
110
|
+
name="information-circle-outline"
|
|
111
|
+
size={16}
|
|
112
|
+
customColor={tokens.colors.textSecondary}
|
|
113
|
+
/>
|
|
114
|
+
<AtomicText style={styles.infoText}>
|
|
115
|
+
{translations.noCreditCharged}
|
|
116
|
+
</AtomicText>
|
|
117
|
+
</View>
|
|
118
|
+
)}
|
|
119
|
+
|
|
120
|
+
<View style={styles.spacer} />
|
|
121
|
+
|
|
122
|
+
<View style={styles.actionsContainer}>
|
|
123
|
+
<AtomicButton
|
|
124
|
+
title={translations.tryAgain}
|
|
125
|
+
onPress={onTryAgain}
|
|
126
|
+
variant="primary"
|
|
127
|
+
size="lg"
|
|
128
|
+
fullWidth
|
|
129
|
+
/>
|
|
130
|
+
|
|
131
|
+
<View style={styles.secondaryButtonContainer}>
|
|
132
|
+
<AtomicButton
|
|
133
|
+
title={translations.chooseAnother}
|
|
134
|
+
onPress={onChooseAnother}
|
|
135
|
+
variant="text"
|
|
136
|
+
size="md"
|
|
137
|
+
fullWidth
|
|
138
|
+
/>
|
|
139
|
+
</View>
|
|
140
|
+
</View>
|
|
141
|
+
</View>
|
|
142
|
+
</ScreenLayout>
|
|
143
|
+
</View>
|
|
144
|
+
);
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
const createStyles = (tokens: DesignTokens) =>
|
|
148
|
+
StyleSheet.create({
|
|
149
|
+
container: {
|
|
150
|
+
flex: 1,
|
|
151
|
+
},
|
|
152
|
+
content: {
|
|
153
|
+
flex: 1,
|
|
154
|
+
alignItems: "center",
|
|
155
|
+
paddingHorizontal: 32,
|
|
156
|
+
paddingTop: 80,
|
|
157
|
+
paddingBottom: 40,
|
|
158
|
+
},
|
|
159
|
+
iconContainer: {
|
|
160
|
+
marginBottom: 24,
|
|
161
|
+
},
|
|
162
|
+
title: {
|
|
163
|
+
...tokens.typography.headingMedium,
|
|
164
|
+
color: tokens.colors.textPrimary,
|
|
165
|
+
fontWeight: "700",
|
|
166
|
+
marginBottom: 12,
|
|
167
|
+
textAlign: "center",
|
|
168
|
+
},
|
|
169
|
+
message: {
|
|
170
|
+
...tokens.typography.bodyMedium,
|
|
171
|
+
color: tokens.colors.textSecondary,
|
|
172
|
+
textAlign: "center",
|
|
173
|
+
lineHeight: 22,
|
|
174
|
+
marginBottom: 24,
|
|
175
|
+
},
|
|
176
|
+
infoContainer: {
|
|
177
|
+
flexDirection: "row",
|
|
178
|
+
alignItems: "center",
|
|
179
|
+
gap: 6,
|
|
180
|
+
opacity: 0.7,
|
|
181
|
+
},
|
|
182
|
+
infoText: {
|
|
183
|
+
...tokens.typography.bodySmall,
|
|
184
|
+
color: tokens.colors.textSecondary,
|
|
185
|
+
},
|
|
186
|
+
spacer: {
|
|
187
|
+
flex: 1,
|
|
188
|
+
},
|
|
189
|
+
actionsContainer: {
|
|
190
|
+
width: "100%",
|
|
191
|
+
gap: 16,
|
|
192
|
+
},
|
|
193
|
+
secondaryButtonContainer: {
|
|
194
|
+
marginTop: 4,
|
|
195
|
+
},
|
|
196
|
+
});
|
|
@@ -6,3 +6,9 @@ export { ResultPreviewScreen } from "./ResultPreviewScreen";
|
|
|
6
6
|
export { ResultImageCard } from "./ResultImageCard";
|
|
7
7
|
export { ResultActionBar } from "./ResultActionBar";
|
|
8
8
|
export { RecentCreationsSection } from "./RecentCreationsSection";
|
|
9
|
+
export { GenerationErrorScreen } from "./GenerationErrorScreen";
|
|
10
|
+
export type {
|
|
11
|
+
GenerationErrorTranslations,
|
|
12
|
+
GenerationErrorConfig,
|
|
13
|
+
GenerationErrorScreenProps,
|
|
14
|
+
} from "./GenerationErrorScreen";
|
|
@@ -41,3 +41,22 @@ export type {
|
|
|
41
41
|
CoupleFeatureOption,
|
|
42
42
|
} from "./infrastructure/coupleFeatureRegistry";
|
|
43
43
|
export { enhanceCouplePrompt } from "./infrastructure/couplePromptEnhancer";
|
|
44
|
+
|
|
45
|
+
// Generation utilities
|
|
46
|
+
export {
|
|
47
|
+
buildGenerationInputFromConfig,
|
|
48
|
+
processGenerationResultFromConfig,
|
|
49
|
+
buildCreationFromConfig,
|
|
50
|
+
DEFAULT_VISUAL_STYLES,
|
|
51
|
+
} from "./infrastructure/generationUtils";
|
|
52
|
+
export type {
|
|
53
|
+
ScenarioConfig,
|
|
54
|
+
VisualStyleConfig,
|
|
55
|
+
GenerationImage,
|
|
56
|
+
BuildGenerationInputConfig,
|
|
57
|
+
GenerationInputResult,
|
|
58
|
+
ProcessResultConfig,
|
|
59
|
+
GenerationResultData,
|
|
60
|
+
BuildCreationConfig,
|
|
61
|
+
CoupleFutureCreationData,
|
|
62
|
+
} from "./infrastructure/generationUtils";
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generation Utilities
|
|
3
|
+
* Config-driven input building and result processing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { enhanceCouplePrompt } from "./couplePromptEnhancer";
|
|
7
|
+
import type { CoupleFeatureSelection } from "../domain/types";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Scenario config interface - app provides this data
|
|
11
|
+
*/
|
|
12
|
+
export interface ScenarioConfig {
|
|
13
|
+
readonly id: string;
|
|
14
|
+
readonly aiPrompt: string;
|
|
15
|
+
readonly storyTemplate: string;
|
|
16
|
+
readonly title?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Visual style modifiers config
|
|
21
|
+
*/
|
|
22
|
+
export interface VisualStyleConfig {
|
|
23
|
+
readonly [key: string]: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const DEFAULT_VISUAL_STYLES: VisualStyleConfig = {
|
|
27
|
+
realistic: "photorealistic, highly detailed, professional photography",
|
|
28
|
+
anime: "anime art style, vibrant colors, manga aesthetic, cel-shaded",
|
|
29
|
+
cinematic: "cinematic lighting, dramatic composition, film photography, movie scene",
|
|
30
|
+
"3d": "3D render, octane render, unreal engine, raytraced lighting",
|
|
31
|
+
} as const;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Uploaded image interface
|
|
35
|
+
*/
|
|
36
|
+
export interface GenerationImage {
|
|
37
|
+
readonly uri: string;
|
|
38
|
+
readonly base64?: string;
|
|
39
|
+
readonly previewUrl?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generation input builder config
|
|
44
|
+
*/
|
|
45
|
+
export interface BuildGenerationInputConfig {
|
|
46
|
+
readonly partnerA: GenerationImage | null;
|
|
47
|
+
readonly partnerB: GenerationImage | null;
|
|
48
|
+
readonly partnerAName: string;
|
|
49
|
+
readonly partnerBName: string;
|
|
50
|
+
readonly scenario: ScenarioConfig | null;
|
|
51
|
+
readonly customPrompt?: string;
|
|
52
|
+
readonly visualStyle: string;
|
|
53
|
+
readonly defaultPartnerAName: string;
|
|
54
|
+
readonly defaultPartnerBName: string;
|
|
55
|
+
readonly coupleFeatureSelection?: CoupleFeatureSelection;
|
|
56
|
+
readonly visualStyles?: VisualStyleConfig;
|
|
57
|
+
readonly customScenarioId?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Generation input with base64
|
|
62
|
+
*/
|
|
63
|
+
export interface GenerationInputResult {
|
|
64
|
+
readonly partnerA: GenerationImage;
|
|
65
|
+
readonly partnerB: GenerationImage;
|
|
66
|
+
readonly partnerABase64: string;
|
|
67
|
+
readonly partnerBBase64: string;
|
|
68
|
+
readonly prompt: string;
|
|
69
|
+
readonly partnerAName: string;
|
|
70
|
+
readonly partnerBName: string;
|
|
71
|
+
readonly scenarioId: string;
|
|
72
|
+
readonly customPrompt?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Build generation input from config
|
|
77
|
+
*/
|
|
78
|
+
export const buildGenerationInputFromConfig = (
|
|
79
|
+
config: BuildGenerationInputConfig,
|
|
80
|
+
): GenerationInputResult | null => {
|
|
81
|
+
const {
|
|
82
|
+
partnerA,
|
|
83
|
+
partnerB,
|
|
84
|
+
partnerAName,
|
|
85
|
+
partnerBName,
|
|
86
|
+
scenario,
|
|
87
|
+
customPrompt,
|
|
88
|
+
visualStyle,
|
|
89
|
+
defaultPartnerAName,
|
|
90
|
+
defaultPartnerBName,
|
|
91
|
+
coupleFeatureSelection = {},
|
|
92
|
+
visualStyles = DEFAULT_VISUAL_STYLES,
|
|
93
|
+
customScenarioId = "custom",
|
|
94
|
+
} = config;
|
|
95
|
+
|
|
96
|
+
if (!partnerA || !partnerB || !scenario) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let prompt = scenario.aiPrompt;
|
|
101
|
+
|
|
102
|
+
if (scenario.id === customScenarioId && customPrompt) {
|
|
103
|
+
prompt = prompt.replace("{{customPrompt}}", customPrompt);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const styleModifier = visualStyles[visualStyle];
|
|
107
|
+
if (visualStyle !== "realistic" && styleModifier) {
|
|
108
|
+
prompt = prompt.replace(/photorealistic/gi, styleModifier);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
prompt = enhanceCouplePrompt(prompt, coupleFeatureSelection);
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
partnerA,
|
|
115
|
+
partnerB,
|
|
116
|
+
partnerABase64: partnerA.base64 || "",
|
|
117
|
+
partnerBBase64: partnerB.base64 || "",
|
|
118
|
+
prompt,
|
|
119
|
+
partnerAName: partnerAName || defaultPartnerAName,
|
|
120
|
+
partnerBName: partnerBName || defaultPartnerBName,
|
|
121
|
+
scenarioId: scenario.id,
|
|
122
|
+
customPrompt,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Process result config
|
|
128
|
+
*/
|
|
129
|
+
export interface ProcessResultConfig {
|
|
130
|
+
readonly scenarioId: string;
|
|
131
|
+
readonly partnerAName: string;
|
|
132
|
+
readonly partnerBName: string;
|
|
133
|
+
readonly scenario: ScenarioConfig;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Generation result
|
|
138
|
+
*/
|
|
139
|
+
export interface GenerationResultData {
|
|
140
|
+
readonly imageUrl: string;
|
|
141
|
+
readonly story: string;
|
|
142
|
+
readonly date: string;
|
|
143
|
+
readonly scenarioId: string;
|
|
144
|
+
readonly names: string;
|
|
145
|
+
readonly timestamp: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Process generation result from config
|
|
150
|
+
*/
|
|
151
|
+
export const processGenerationResultFromConfig = (
|
|
152
|
+
imageUrl: string,
|
|
153
|
+
config: ProcessResultConfig,
|
|
154
|
+
): GenerationResultData => {
|
|
155
|
+
const { scenario, partnerAName, partnerBName, scenarioId } = config;
|
|
156
|
+
|
|
157
|
+
const futureYear = new Date().getFullYear() + 5 + Math.floor(Math.random() * 20);
|
|
158
|
+
const months = ["May", "June", "August"];
|
|
159
|
+
const randomMonth = months[Math.floor(Math.random() * months.length)];
|
|
160
|
+
const randomDay = Math.floor(Math.random() * 28) + 1;
|
|
161
|
+
|
|
162
|
+
const story = scenario.storyTemplate
|
|
163
|
+
.replace(/\{\{partnerA\}\}/g, partnerAName)
|
|
164
|
+
.replace(/\{\{partnerB\}\}/g, partnerBName)
|
|
165
|
+
.replace(/\{\{year\}\}/g, String(futureYear));
|
|
166
|
+
|
|
167
|
+
return {
|
|
168
|
+
imageUrl,
|
|
169
|
+
story,
|
|
170
|
+
date: `${randomMonth} ${randomDay}, ${futureYear}`,
|
|
171
|
+
scenarioId,
|
|
172
|
+
names: `${partnerAName} & ${partnerBName}`,
|
|
173
|
+
timestamp: Date.now(),
|
|
174
|
+
};
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Build creation config
|
|
179
|
+
*/
|
|
180
|
+
export interface BuildCreationConfig {
|
|
181
|
+
readonly scenarioId: string;
|
|
182
|
+
readonly partnerAName: string;
|
|
183
|
+
readonly partnerBName: string;
|
|
184
|
+
readonly scenarioTitle?: string;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Creation data
|
|
189
|
+
*/
|
|
190
|
+
export interface CoupleFutureCreationData {
|
|
191
|
+
readonly id: string;
|
|
192
|
+
readonly uri: string;
|
|
193
|
+
readonly type: string;
|
|
194
|
+
readonly prompt: string;
|
|
195
|
+
readonly metadata: {
|
|
196
|
+
readonly scenarioId: string;
|
|
197
|
+
readonly partnerAName: string;
|
|
198
|
+
readonly partnerBName: string;
|
|
199
|
+
};
|
|
200
|
+
readonly createdAt: Date;
|
|
201
|
+
readonly isShared: boolean;
|
|
202
|
+
readonly isFavorite: boolean;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Build creation from result
|
|
207
|
+
*/
|
|
208
|
+
export const buildCreationFromConfig = (
|
|
209
|
+
result: GenerationResultData,
|
|
210
|
+
config: BuildCreationConfig,
|
|
211
|
+
): CoupleFutureCreationData => {
|
|
212
|
+
const { scenarioId, partnerAName, partnerBName, scenarioTitle } = config;
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
id: `${scenarioId}_${Date.now()}`,
|
|
216
|
+
uri: result.imageUrl,
|
|
217
|
+
type: scenarioId,
|
|
218
|
+
prompt: scenarioTitle || scenarioId,
|
|
219
|
+
metadata: {
|
|
220
|
+
scenarioId,
|
|
221
|
+
partnerAName,
|
|
222
|
+
partnerBName,
|
|
223
|
+
},
|
|
224
|
+
createdAt: new Date(),
|
|
225
|
+
isShared: false,
|
|
226
|
+
isFavorite: false,
|
|
227
|
+
};
|
|
228
|
+
};
|