@umituz/react-native-ai-generation-content 1.89.33 → 1.89.35

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.89.33",
3
+ "version": "1.89.35",
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",
@@ -7,9 +7,10 @@ import type { WizardScenarioData } from "../../presentation/hooks/useWizardGener
7
7
  import type { WizardStrategy } from "./wizard-strategy.types";
8
8
  import { DEFAULT_STYLE_VALUE, IMAGE_PROCESSING_PROMPTS } from "./wizard-strategy.constants";
9
9
  import { extractPrompt, extractSelection } from "../utils";
10
- import { extractPhotosAsBase64 } from "./shared/photo-extraction.utils";
10
+ import { extractPhotosAsBase64, extractPhotoUris } from "./shared/photo-extraction.utils";
11
11
  import { executeImageGeneration } from "./image-generation.executor";
12
12
  import type { WizardImageInput, CreateImageStrategyOptions } from "./image-generation.types";
13
+ import { enhancePromptWithAnalysis } from "../../../infrastructure/appearance-analysis";
13
14
 
14
15
 
15
16
  // ============================================================================
@@ -20,6 +21,8 @@ export async function buildImageInput(
20
21
  wizardData: Record<string, unknown>,
21
22
  scenario: WizardScenarioData,
22
23
  ): Promise<WizardImageInput | null> {
24
+ // Extract photo URIs first (for couple refinement)
25
+ const photoUris = extractPhotoUris(wizardData);
23
26
  const photos = await extractPhotosAsBase64(wizardData);
24
27
 
25
28
  // Extract prompt with fallback to default
@@ -38,6 +41,20 @@ export async function buildImageInput(
38
41
  let finalPrompt = prompt;
39
42
  if (photos.length > 0) {
40
43
  finalPrompt = applyStyleEnhancements(prompt, wizardData);
44
+
45
+ // ✅ Apply couple refinement (same logic as Wardrobe)
46
+ // This ensures consistency across all couple generation scenarios
47
+ const isCoupleMode = photos.length >= 2;
48
+ finalPrompt = await enhancePromptWithAnalysis(finalPrompt, photoUris, isCoupleMode);
49
+
50
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
51
+ console.log("[ImageStrategy] Prompt enhanced with couple refinement", {
52
+ photoCount: photos.length,
53
+ isCoupleMode,
54
+ originalPromptLength: prompt.length,
55
+ finalPromptLength: finalPrompt.length,
56
+ });
57
+ }
41
58
  }
42
59
 
43
60
  // Extract style for text-to-image
@@ -3,9 +3,11 @@
3
3
  * Shared photo extraction logic for wizard strategies
4
4
  *
5
5
  * Resize strategy:
6
- * - Small images (<300px): scale UP to 300px minimum (AI provider requirement)
6
+ * - Small images (<768px): scale UP to 768px minimum (good face preservation)
7
7
  * - Large images (>1536px): scale DOWN to 1536px maximum (reduces upload size ~10x)
8
8
  * - Normal images: pass through unchanged
9
+ *
10
+ * IMPORTANT: 768px minimum ensures AI models have enough detail for face preservation
9
11
  */
10
12
 
11
13
  import { readFileAsBase64 } from "@umituz/react-native-design-system/filesystem";
@@ -14,7 +16,7 @@ import { Image } from "react-native";
14
16
  import { PHOTO_KEY_PREFIX } from "../wizard-strategy.constants";
15
17
 
16
18
 
17
- const MIN_IMAGE_DIMENSION = 300;
19
+ const MIN_IMAGE_DIMENSION = 768; // Minimum for good face preservation
18
20
  const MAX_IMAGE_DIMENSION = 1536;
19
21
 
20
22
  /**
@@ -28,7 +30,7 @@ function getImageSize(uri: string): Promise<{ width: number; height: number }> {
28
30
 
29
31
  /**
30
32
  * Ensure image is within optimal dimensions for AI generation.
31
- * - Too small (<300px): scale up (AI providers require minimum dimensions)
33
+ * - Too small (<768px): scale up (good face preservation)
32
34
  * - Too large (>1536px): scale down (reduces upload size, prevents timeouts)
33
35
  * - Within range: return as-is
34
36
  */
@@ -37,49 +39,85 @@ async function ensureOptimalSize(uri: string): Promise<string> {
37
39
  const { width, height } = await getImageSize(uri);
38
40
  const maxDim = Math.max(width, height);
39
41
 
42
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
43
+ console.log("[PhotoExtraction] Analyzing image", {
44
+ originalDimensions: `${width}x${height}`,
45
+ maxDim,
46
+ minDim: Math.min(width, height),
47
+ isTooSmall: width < MIN_IMAGE_DIMENSION || height < MIN_IMAGE_DIMENSION,
48
+ isTooLarge: maxDim > MAX_IMAGE_DIMENSION,
49
+ });
50
+ }
51
+
40
52
  // Already within optimal range
41
53
  if (width >= MIN_IMAGE_DIMENSION && height >= MIN_IMAGE_DIMENSION && maxDim <= MAX_IMAGE_DIMENSION) {
54
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
55
+ console.log("[PhotoExtraction] Image already optimal, skipping resize", {
56
+ dimensions: `${width}x${height}`,
57
+ minRequired: MIN_IMAGE_DIMENSION,
58
+ maxAllowed: MAX_IMAGE_DIMENSION,
59
+ });
60
+ }
42
61
  return uri;
43
62
  }
44
63
 
45
64
  let newWidth: number;
46
65
  let newHeight: number;
66
+ let direction: string;
47
67
 
48
68
  if (maxDim > MAX_IMAGE_DIMENSION) {
49
69
  // Scale DOWN — largest dimension becomes MAX_IMAGE_DIMENSION
50
70
  const scale = MAX_IMAGE_DIMENSION / maxDim;
51
71
  newWidth = Math.round(width * scale);
52
72
  newHeight = Math.round(height * scale);
73
+ direction = "down";
53
74
  } else {
54
75
  // Scale UP — smallest dimension becomes MIN_IMAGE_DIMENSION
55
76
  const scale = Math.max(MIN_IMAGE_DIMENSION / width, MIN_IMAGE_DIMENSION / height);
56
77
  newWidth = Math.ceil(width * scale);
57
78
  newHeight = Math.ceil(height * scale);
79
+ direction = "up";
58
80
  }
59
81
 
82
+ const compressQuality = maxDim > MAX_IMAGE_DIMENSION ? 0.8 : 1.0; // Lossless for scale-up
83
+
60
84
  if (typeof __DEV__ !== "undefined" && __DEV__) {
61
- const direction = maxDim > MAX_IMAGE_DIMENSION ? "down" : "up";
62
85
  console.log(`[PhotoExtraction] Resizing ${direction}`, {
63
86
  from: `${width}x${height}`,
64
87
  to: `${newWidth}x${newHeight}`,
88
+ scaleChange: `${((Math.max(newWidth, newHeight) / maxDim - 1) * 100).toFixed(0)}%`,
89
+ compressQuality,
65
90
  });
66
91
  }
67
92
 
68
93
  const result = await manipulateAsync(uri, [{ resize: { width: newWidth, height: newHeight } }], {
69
94
  format: SaveFormat.JPEG,
70
- compress: maxDim > MAX_IMAGE_DIMENSION ? 0.8 : 0.9,
95
+ compress: compressQuality,
71
96
  });
72
97
 
98
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
99
+ console.log("[PhotoExtraction] Resize complete", {
100
+ original: `${width}x${height}`,
101
+ resized: `${newWidth}x${newHeight}`,
102
+ action: direction,
103
+ quality: compressQuality === 1.0 ? "lossless" : `${compressQuality * 100}%`,
104
+ });
105
+ }
106
+
73
107
  return result.uri;
74
- } catch {
108
+ } catch (error) {
109
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
110
+ console.error("[PhotoExtraction] Resize failed, using original", error);
111
+ }
75
112
  return uri;
76
113
  }
77
114
  }
78
115
 
79
116
  /**
80
117
  * Extracts photo URIs from wizard data
118
+ * Exported for use in other strategies (e.g., couple refinement)
81
119
  */
82
- function extractPhotoUris(wizardData: Record<string, unknown>): string[] {
120
+ export function extractPhotoUris(wizardData: Record<string, unknown>): string[] {
83
121
  const photoKeys = Object.keys(wizardData)
84
122
  .filter((k) => k.includes(PHOTO_KEY_PREFIX))
85
123
  .sort();
@@ -108,18 +146,27 @@ export async function extractPhotosAsBase64(
108
146
  enableDebugLogs = false,
109
147
  ): Promise<string[]> {
110
148
  if (enableDebugLogs && typeof __DEV__ !== "undefined" && __DEV__) {
111
- console.log("[PhotoExtraction] Starting extraction", {
149
+ console.log("[PhotoExtraction] >>> extractPhotosAsBase64 START", {
112
150
  wizardDataKeys: Object.keys(wizardData),
151
+ photoKeyPrefix: PHOTO_KEY_PREFIX,
152
+ minDimension: MIN_IMAGE_DIMENSION,
153
+ maxDimension: MAX_IMAGE_DIMENSION,
113
154
  });
114
155
  }
115
156
 
116
157
  const photoUris = extractPhotoUris(wizardData);
117
158
 
118
159
  if (enableDebugLogs && typeof __DEV__ !== "undefined" && __DEV__) {
119
- console.log("[PhotoExtraction] Found photo URIs", { count: photoUris.length });
160
+ console.log("[PhotoExtraction] Photo URIs extracted", {
161
+ count: photoUris.length,
162
+ keys: Object.keys(wizardData).filter(k => k.includes(PHOTO_KEY_PREFIX)),
163
+ });
120
164
  }
121
165
 
122
166
  if (photoUris.length === 0) {
167
+ if (enableDebugLogs && typeof __DEV__ !== "undefined" && __DEV__) {
168
+ console.log("[PhotoExtraction] No photos found, returning empty array");
169
+ }
123
170
  return [];
124
171
  }
125
172
 
@@ -127,11 +174,29 @@ export async function extractPhotosAsBase64(
127
174
  const results = await Promise.allSettled(
128
175
  photoUris.map(async (uri, index) => {
129
176
  try {
177
+ if (enableDebugLogs && typeof __DEV__ !== "undefined" && __DEV__) {
178
+ console.log(`[PhotoExtraction] Processing photo ${index + 1}/${photoUris.length}`, {
179
+ uri: uri.substring(0, 50) + "...",
180
+ });
181
+ }
182
+
130
183
  const optimizedUri = await ensureOptimalSize(uri);
131
- return await readFileAsBase64(optimizedUri);
184
+ const base64 = await readFileAsBase64(optimizedUri);
185
+
186
+ if (enableDebugLogs && typeof __DEV__ !== "undefined" && __DEV__) {
187
+ console.log(`[PhotoExtraction] Photo ${index + 1} processed`, {
188
+ sizeKB: (base64.length / 1024).toFixed(1),
189
+ originalUri: uri.substring(0, 30) + "...",
190
+ });
191
+ }
192
+
193
+ return base64;
132
194
  } catch (error) {
133
195
  if (typeof __DEV__ !== "undefined" && __DEV__) {
134
- console.error(`[PhotoExtraction] Failed to read photo ${index}:`, error);
196
+ console.error(`[PhotoExtraction] Failed to process photo ${index + 1}:`, {
197
+ uri: uri.substring(0, 50) + "...",
198
+ error: error instanceof Error ? error.message : String(error),
199
+ });
135
200
  }
136
201
  return null;
137
202
  }
@@ -143,15 +208,21 @@ export async function extractPhotosAsBase64(
143
208
  .map((result) => (result.status === "fulfilled" ? result.value : null))
144
209
  .filter((photo): photo is string => typeof photo === "string" && photo.length > 0);
145
210
 
211
+ const failedCount = results.filter((r) => r.status === "rejected").length;
212
+
146
213
  if (enableDebugLogs && typeof __DEV__ !== "undefined" && __DEV__) {
147
- const failedCount = results.filter((r) => r.status === "rejected").length;
148
- console.log("[PhotoExtraction] Converted photos", {
214
+ console.log("[PhotoExtraction] <<< extractPhotosAsBase64 COMPLETE", {
149
215
  total: photoUris.length,
150
- valid: validPhotos.length,
216
+ successful: validPhotos.length,
151
217
  failed: failedCount,
152
- sizes: validPhotos.map((p) => `${(p.length / 1024).toFixed(1)}KB`),
218
+ sizes: validPhotos.map((p, i) => `Photo ${i + 1}: ${(p.length / 1024).toFixed(1)}KB`),
219
+ totalSizeMB: ((validPhotos.reduce((sum, p) => sum + p.length, 0) / 1024 / 1024).toFixed(2)),
153
220
  });
154
221
  }
155
222
 
223
+ if (failedCount > 0 && typeof __DEV__ !== "undefined" && __DEV__) {
224
+ console.warn(`[PhotoExtraction] ⚠️ ${failedCount} photo(s) failed to process`);
225
+ }
226
+
156
227
  return validPhotos;
157
228
  }