@umituz/react-native-ai-generation-content 1.27.14 → 1.27.16
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 +3 -4
- package/src/domains/generation/wizard/configs/image-to-video.config.ts +39 -0
- package/src/domains/generation/wizard/configs/index.ts +8 -0
- package/src/domains/generation/wizard/configs/text-to-image.config.ts +29 -0
- package/src/domains/generation/wizard/configs/text-to-video.config.ts +33 -0
- package/src/domains/generation/wizard/index.ts +3 -0
- package/src/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts +76 -55
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts +30 -20
- package/src/domains/generation/wizard/infrastructure/strategies/wizard-strategy.constants.ts +2 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.27.
|
|
3
|
+
"version": "1.27.16",
|
|
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",
|
|
@@ -38,8 +38,7 @@
|
|
|
38
38
|
"url": "git+https://github.com/umituz/react-native-ai-generation-content.git"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@umituz/react-native-auth": "
|
|
42
|
-
"@umituz/react-native-firebase": "*"
|
|
41
|
+
"@umituz/react-native-auth": "^3.6.25"
|
|
43
42
|
},
|
|
44
43
|
"peerDependencies": {
|
|
45
44
|
"@react-navigation/native": ">=6.0.0",
|
|
@@ -68,7 +67,7 @@
|
|
|
68
67
|
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
69
68
|
"@typescript-eslint/parser": "^8.0.0",
|
|
70
69
|
"@umituz/react-native-design-system": "^2.9.44",
|
|
71
|
-
"@umituz/react-native-firebase": "
|
|
70
|
+
"@umituz/react-native-firebase": "^1.13.87",
|
|
72
71
|
"@umituz/react-native-localization": "*",
|
|
73
72
|
"@umituz/react-native-subscription": "*",
|
|
74
73
|
"eslint": "^9.0.0",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image to Video Wizard Config
|
|
3
|
+
* Config-driven wizard steps for image-to-video generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { WizardFeatureConfig } from "../domain/entities/wizard-config.types";
|
|
7
|
+
|
|
8
|
+
export const IMAGE_TO_VIDEO_WIZARD_CONFIG: WizardFeatureConfig = {
|
|
9
|
+
id: "image-to-video",
|
|
10
|
+
name: "Image to Video",
|
|
11
|
+
steps: [
|
|
12
|
+
{
|
|
13
|
+
id: "photo_1",
|
|
14
|
+
type: "photo_upload",
|
|
15
|
+
label: "Your Photo",
|
|
16
|
+
showFaceDetection: false,
|
|
17
|
+
showPhotoTips: true,
|
|
18
|
+
required: true,
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "motion_prompt",
|
|
22
|
+
type: "text_input",
|
|
23
|
+
required: false,
|
|
24
|
+
placeholderKey: "imageToVideo.motionPromptPlaceholder",
|
|
25
|
+
maxLength: 200,
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
id: "duration",
|
|
29
|
+
type: "selection",
|
|
30
|
+
selectionType: "duration",
|
|
31
|
+
options: [
|
|
32
|
+
{ id: "5s", label: "5 seconds", value: 5 },
|
|
33
|
+
{ id: "10s", label: "10 seconds", value: 10 },
|
|
34
|
+
],
|
|
35
|
+
required: true,
|
|
36
|
+
defaultValue: "5s",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wizard Feature Configs
|
|
3
|
+
* Pre-built configs for common generation features
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export { TEXT_TO_IMAGE_WIZARD_CONFIG } from "./text-to-image.config";
|
|
7
|
+
export { TEXT_TO_VIDEO_WIZARD_CONFIG } from "./text-to-video.config";
|
|
8
|
+
export { IMAGE_TO_VIDEO_WIZARD_CONFIG } from "./image-to-video.config";
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text to Image Wizard Config
|
|
3
|
+
* Config-driven wizard steps for text-to-image generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { WizardFeatureConfig } from "../domain/entities/wizard-config.types";
|
|
7
|
+
|
|
8
|
+
export const TEXT_TO_IMAGE_WIZARD_CONFIG: WizardFeatureConfig = {
|
|
9
|
+
id: "text-to-image",
|
|
10
|
+
name: "Text to Image",
|
|
11
|
+
steps: [
|
|
12
|
+
{
|
|
13
|
+
id: "prompt",
|
|
14
|
+
type: "text_input",
|
|
15
|
+
required: true,
|
|
16
|
+
placeholderKey: "textToImage.promptPlaceholder",
|
|
17
|
+
minLength: 3,
|
|
18
|
+
maxLength: 1000,
|
|
19
|
+
multiline: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "style",
|
|
23
|
+
type: "selection",
|
|
24
|
+
selectionType: "style",
|
|
25
|
+
options: [],
|
|
26
|
+
required: false,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text to Video Wizard Config
|
|
3
|
+
* Config-driven wizard steps for text-to-video generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { WizardFeatureConfig } from "../domain/entities/wizard-config.types";
|
|
7
|
+
|
|
8
|
+
export const TEXT_TO_VIDEO_WIZARD_CONFIG: WizardFeatureConfig = {
|
|
9
|
+
id: "text-to-video",
|
|
10
|
+
name: "Text to Video",
|
|
11
|
+
steps: [
|
|
12
|
+
{
|
|
13
|
+
id: "prompt",
|
|
14
|
+
type: "text_input",
|
|
15
|
+
required: true,
|
|
16
|
+
placeholderKey: "textToVideo.promptPlaceholder",
|
|
17
|
+
minLength: 3,
|
|
18
|
+
maxLength: 500,
|
|
19
|
+
multiline: true,
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "duration",
|
|
23
|
+
type: "selection",
|
|
24
|
+
selectionType: "duration",
|
|
25
|
+
options: [
|
|
26
|
+
{ id: "5s", label: "5 seconds", value: 5 },
|
|
27
|
+
{ id: "10s", label: "10 seconds", value: 10 },
|
|
28
|
+
],
|
|
29
|
+
required: true,
|
|
30
|
+
defaultValue: "5s",
|
|
31
|
+
},
|
|
32
|
+
],
|
|
33
|
+
};
|
package/src/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts
CHANGED
|
@@ -24,10 +24,13 @@ declare const __DEV__: boolean;
|
|
|
24
24
|
// ============================================================================
|
|
25
25
|
|
|
26
26
|
export interface ImageGenerationInput {
|
|
27
|
+
/** Photos are optional for text-to-image */
|
|
27
28
|
readonly photos: readonly string[];
|
|
28
29
|
readonly prompt: string;
|
|
29
30
|
/** Optional interaction style for multi-person images */
|
|
30
31
|
readonly interactionStyle?: InteractionStyle;
|
|
32
|
+
/** Optional style from wizard selection */
|
|
33
|
+
readonly style?: string;
|
|
31
34
|
}
|
|
32
35
|
|
|
33
36
|
export interface ImageGenerationResult {
|
|
@@ -40,29 +43,29 @@ export interface ImageGenerationResult {
|
|
|
40
43
|
|
|
41
44
|
async function extractPhotosFromWizardData(
|
|
42
45
|
wizardData: Record<string, unknown>,
|
|
43
|
-
): Promise<string[]
|
|
46
|
+
): Promise<string[]> {
|
|
44
47
|
const photoKeys = Object.keys(wizardData)
|
|
45
48
|
.filter((k) => k.includes(PHOTO_KEY_PREFIX))
|
|
46
49
|
.sort();
|
|
47
50
|
|
|
48
51
|
if (photoKeys.length === 0) {
|
|
49
|
-
|
|
50
|
-
console.error("[ImageStrategy] No photos found", { keys: Object.keys(wizardData) });
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
52
|
+
return [];
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
const photoUris: string[] = [];
|
|
56
56
|
for (const key of photoKeys) {
|
|
57
57
|
const photo = wizardData[key] as { uri?: string };
|
|
58
|
-
if (
|
|
59
|
-
|
|
58
|
+
if (photo?.uri) {
|
|
59
|
+
photoUris.push(photo.uri);
|
|
60
|
+
}
|
|
60
61
|
}
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
if (photoUris.length === 0) {
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
const photosBase64 = await Promise.all(photoUris.map((uri) => readFileAsBase64(uri)));
|
|
68
|
+
return photosBase64.filter(Boolean) as string[];
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
// ============================================================================
|
|
@@ -85,43 +88,53 @@ async function executeImageGeneration(
|
|
|
85
88
|
const formatBase64 = (base64: string): string =>
|
|
86
89
|
base64.startsWith("data:") ? base64 : `${BASE64_IMAGE_PREFIX}${base64}`;
|
|
87
90
|
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
return { success: false, error: "At least one image required" };
|
|
91
|
-
}
|
|
91
|
+
const hasPhotos = input.photos.length > 0;
|
|
92
|
+
const imageUrls = hasPhotos ? input.photos.map(formatBase64) : [];
|
|
92
93
|
|
|
93
|
-
|
|
94
|
-
const facePrompt = buildFacePreservationPrompt({
|
|
95
|
-
scenarioPrompt: input.prompt,
|
|
96
|
-
personCount: imageUrls.length,
|
|
97
|
-
});
|
|
94
|
+
let finalPrompt = input.prompt;
|
|
98
95
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// Combine prompts: face preservation + interaction style
|
|
106
|
-
const enhancedPrompt = interactionPrompt
|
|
107
|
-
? `${facePrompt}\n\n${interactionPrompt}`
|
|
108
|
-
: facePrompt;
|
|
96
|
+
if (hasPhotos) {
|
|
97
|
+
// Photo-based: Build face preservation prompt
|
|
98
|
+
const facePrompt = buildFacePreservationPrompt({
|
|
99
|
+
scenarioPrompt: input.prompt,
|
|
100
|
+
personCount: imageUrls.length,
|
|
101
|
+
});
|
|
109
102
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
103
|
+
const interactionPrompt = buildInteractionStylePrompt({
|
|
104
|
+
style: input.interactionStyle ?? "romantic",
|
|
105
|
+
personCount: imageUrls.length,
|
|
113
106
|
});
|
|
107
|
+
|
|
108
|
+
finalPrompt = interactionPrompt
|
|
109
|
+
? `${facePrompt}\n\n${interactionPrompt}`
|
|
110
|
+
: facePrompt;
|
|
111
|
+
|
|
112
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
113
|
+
console.log("[ImageStrategy] Photo-based generation for", imageUrls.length, "person(s)");
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
// Text-to-image: Use prompt with optional style
|
|
117
|
+
if (input.style && input.style !== DEFAULT_STYLE_VALUE) {
|
|
118
|
+
finalPrompt = `${input.prompt}. Style: ${input.style}`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
122
|
+
console.log("[ImageStrategy] Text-to-image generation");
|
|
123
|
+
}
|
|
114
124
|
}
|
|
115
125
|
|
|
116
|
-
const modelInput = {
|
|
117
|
-
|
|
118
|
-
prompt: enhancedPrompt,
|
|
126
|
+
const modelInput: Record<string, unknown> = {
|
|
127
|
+
prompt: finalPrompt,
|
|
119
128
|
aspect_ratio: MODEL_INPUT_DEFAULTS.aspectRatio,
|
|
120
129
|
output_format: MODEL_INPUT_DEFAULTS.outputFormat,
|
|
121
130
|
num_images: MODEL_INPUT_DEFAULTS.numImages,
|
|
122
131
|
enable_safety_checker: MODEL_INPUT_DEFAULTS.enableSafetyChecker,
|
|
123
132
|
};
|
|
124
133
|
|
|
134
|
+
if (hasPhotos) {
|
|
135
|
+
modelInput.image_urls = imageUrls;
|
|
136
|
+
}
|
|
137
|
+
|
|
125
138
|
let lastStatus = "";
|
|
126
139
|
const result = await provider.subscribe(model, modelInput, {
|
|
127
140
|
timeoutMs: GENERATION_TIMEOUT_MS,
|
|
@@ -151,39 +164,47 @@ export async function buildImageInput(
|
|
|
151
164
|
scenario: WizardScenarioData,
|
|
152
165
|
): Promise<ImageGenerationInput | null> {
|
|
153
166
|
const photos = await extractPhotosFromWizardData(wizardData);
|
|
154
|
-
if (!photos) return null;
|
|
155
167
|
|
|
156
|
-
|
|
157
|
-
|
|
168
|
+
// Get prompt from wizardData (text_input step) OR scenario.aiPrompt
|
|
169
|
+
const userPrompt = wizardData.prompt as string | undefined;
|
|
170
|
+
const prompt = userPrompt?.trim() || scenario.aiPrompt?.trim();
|
|
171
|
+
|
|
172
|
+
if (!prompt) {
|
|
173
|
+
throw new Error("Prompt is required for image generation");
|
|
158
174
|
}
|
|
159
175
|
|
|
160
|
-
|
|
176
|
+
// For photo-based generation, apply style enhancements
|
|
177
|
+
let finalPrompt = prompt;
|
|
178
|
+
if (photos.length > 0) {
|
|
179
|
+
const styleEnhancements: string[] = [];
|
|
161
180
|
|
|
162
|
-
|
|
181
|
+
const romanticMoods = wizardData.selection_romantic_mood as string[] | undefined;
|
|
182
|
+
if (romanticMoods?.length) {
|
|
183
|
+
styleEnhancements.push(`Mood: ${romanticMoods.join(", ")}`);
|
|
184
|
+
}
|
|
163
185
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
186
|
+
const artStyle = wizardData.selection_art_style as string | undefined;
|
|
187
|
+
if (artStyle && artStyle !== DEFAULT_STYLE_VALUE) {
|
|
188
|
+
styleEnhancements.push(`Art style: ${artStyle}`);
|
|
189
|
+
}
|
|
168
190
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
191
|
+
const artist = wizardData.selection_artist_style as string | undefined;
|
|
192
|
+
if (artist && artist !== DEFAULT_STYLE_VALUE) {
|
|
193
|
+
styleEnhancements.push(`Artist style: ${artist}`);
|
|
194
|
+
}
|
|
173
195
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
196
|
+
if (styleEnhancements.length > 0) {
|
|
197
|
+
finalPrompt = `${prompt}. ${styleEnhancements.join(", ")}`;
|
|
198
|
+
}
|
|
177
199
|
}
|
|
178
200
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
201
|
+
// Get style from wizard selection (for text-to-image)
|
|
202
|
+
const style = wizardData.style as string | undefined;
|
|
182
203
|
|
|
183
204
|
// Get interaction style from scenario (default: romantic for couple apps)
|
|
184
205
|
const interactionStyle = (scenario.interactionStyle as InteractionStyle) ?? "romantic";
|
|
185
206
|
|
|
186
|
-
return { photos, prompt, interactionStyle };
|
|
207
|
+
return { photos, prompt: finalPrompt, style, interactionStyle };
|
|
187
208
|
}
|
|
188
209
|
|
|
189
210
|
// ============================================================================
|
package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts
CHANGED
|
@@ -11,16 +11,18 @@ import type { WizardScenarioData } from "../../presentation/hooks/useWizardGener
|
|
|
11
11
|
import type { WizardStrategy } from "./wizard-strategy.types";
|
|
12
12
|
import { PHOTO_KEY_PREFIX, VIDEO_FEATURE_PATTERNS } from "./wizard-strategy.constants";
|
|
13
13
|
|
|
14
|
-
declare const __DEV__: boolean;
|
|
15
|
-
|
|
16
14
|
// ============================================================================
|
|
17
15
|
// Types
|
|
18
16
|
// ============================================================================
|
|
19
17
|
|
|
20
18
|
export interface VideoGenerationInput {
|
|
21
|
-
|
|
22
|
-
readonly
|
|
19
|
+
/** Source image (optional for text-to-video) */
|
|
20
|
+
readonly sourceImageBase64?: string;
|
|
21
|
+
/** Target image (optional, uses source if not provided) */
|
|
22
|
+
readonly targetImageBase64?: string;
|
|
23
23
|
readonly prompt: string;
|
|
24
|
+
/** Video duration in seconds */
|
|
25
|
+
readonly duration?: number;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export interface VideoGenerationResult {
|
|
@@ -33,29 +35,29 @@ export interface VideoGenerationResult {
|
|
|
33
35
|
|
|
34
36
|
async function extractPhotosFromWizardData(
|
|
35
37
|
wizardData: Record<string, unknown>,
|
|
36
|
-
): Promise<string[]
|
|
38
|
+
): Promise<string[]> {
|
|
37
39
|
const photoKeys = Object.keys(wizardData)
|
|
38
40
|
.filter((k) => k.includes(PHOTO_KEY_PREFIX))
|
|
39
41
|
.sort();
|
|
40
42
|
|
|
41
43
|
if (photoKeys.length === 0) {
|
|
42
|
-
|
|
43
|
-
console.error("[VideoStrategy] No photos found", { keys: Object.keys(wizardData) });
|
|
44
|
-
}
|
|
45
|
-
return null;
|
|
44
|
+
return [];
|
|
46
45
|
}
|
|
47
46
|
|
|
48
47
|
const photoUris: string[] = [];
|
|
49
48
|
for (const key of photoKeys) {
|
|
50
49
|
const photo = wizardData[key] as { uri?: string };
|
|
51
|
-
if (
|
|
52
|
-
|
|
50
|
+
if (photo?.uri) {
|
|
51
|
+
photoUris.push(photo.uri);
|
|
52
|
+
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
if (photoUris.length === 0) {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
57
58
|
|
|
58
|
-
|
|
59
|
+
const photosBase64 = await Promise.all(photoUris.map((uri) => readFileAsBase64(uri)));
|
|
60
|
+
return photosBase64.filter(Boolean) as string[];
|
|
59
61
|
}
|
|
60
62
|
|
|
61
63
|
// ============================================================================
|
|
@@ -83,16 +85,24 @@ export async function buildVideoInput(
|
|
|
83
85
|
scenario: WizardScenarioData,
|
|
84
86
|
): Promise<VideoGenerationInput | null> {
|
|
85
87
|
const photos = await extractPhotosFromWizardData(wizardData);
|
|
86
|
-
if (!photos || photos.length < 1) return null;
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
89
|
+
// Get prompt from wizardData or scenario
|
|
90
|
+
const userPrompt = wizardData.prompt as string | undefined;
|
|
91
|
+
const motionPrompt = wizardData.motion_prompt as string | undefined;
|
|
92
|
+
const prompt = userPrompt?.trim() || motionPrompt?.trim() || scenario.aiPrompt?.trim();
|
|
93
|
+
|
|
94
|
+
if (!prompt) {
|
|
95
|
+
throw new Error("Prompt is required for video generation");
|
|
90
96
|
}
|
|
91
97
|
|
|
98
|
+
// Get duration from wizardData
|
|
99
|
+
const duration = wizardData.duration as number | undefined;
|
|
100
|
+
|
|
92
101
|
return {
|
|
93
102
|
sourceImageBase64: photos[0],
|
|
94
103
|
targetImageBase64: photos[1] || photos[0],
|
|
95
|
-
prompt
|
|
104
|
+
prompt,
|
|
105
|
+
duration,
|
|
96
106
|
};
|
|
97
107
|
}
|
|
98
108
|
|
|
@@ -120,8 +130,8 @@ export function createVideoStrategy(options: CreateVideoStrategyOptions): Wizard
|
|
|
120
130
|
const result = await executeVideoFeature(
|
|
121
131
|
videoFeatureType,
|
|
122
132
|
{
|
|
123
|
-
sourceImageBase64: videoInput.sourceImageBase64,
|
|
124
|
-
targetImageBase64: videoInput.targetImageBase64,
|
|
133
|
+
sourceImageBase64: videoInput.sourceImageBase64 || "",
|
|
134
|
+
targetImageBase64: videoInput.targetImageBase64 || videoInput.sourceImageBase64 || "",
|
|
125
135
|
prompt: videoInput.prompt,
|
|
126
136
|
},
|
|
127
137
|
{ onProgress },
|