@umituz/react-native-ai-generation-content 1.61.3 → 1.61.5

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.61.3",
3
+ "version": "1.61.5",
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",
package/src/index.ts CHANGED
@@ -47,13 +47,14 @@ export {
47
47
  export {
48
48
  providerRegistry, generationOrchestrator, pollJob, createJobPoller,
49
49
  executeImageFeature, hasImageFeatureSupport, executeVideoFeature, hasVideoFeatureSupport,
50
- submitVideoFeatureToQueue,
50
+ submitVideoFeatureToQueue, executeMultiImageGeneration,
51
51
  } from "./infrastructure/services";
52
52
 
53
53
  export type {
54
54
  OrchestratorConfig, PollJobOptions, PollJobResult, ImageResultExtractor,
55
55
  ExecuteImageFeatureOptions, ImageFeatureResult, ImageFeatureRequest,
56
56
  ExecuteVideoFeatureOptions, VideoFeatureResult, VideoFeatureRequest,
57
+ MultiImageGenerationInput, MultiImageGenerationResult,
57
58
  } from "./infrastructure/services";
58
59
 
59
60
  export {
@@ -63,7 +64,6 @@ export {
63
64
  extractAudioUrl, extractImageUrls, cleanBase64, addBase64Prefix, preparePhoto, preparePhotos,
64
65
  isValidBase64, getBase64Size, getBase64SizeMB, prepareImage, createDevCallbacks, createFeatureUtils,
65
66
  showVideoGenerationSuccess, handleGenerationError, showContentModerationWarning,
66
- saveMediaToGallery, shareMedia, createSaveHandler, createShareHandler, createMediaHandlers,
67
67
  mapJobStatusToGenerationStatus,
68
68
  } from "./infrastructure/utils";
69
69
 
@@ -72,14 +72,13 @@ export { distinctBy } from "./utils/arrayUtils";
72
72
  export type {
73
73
  IntervalOptions, StatusCheckResult, ResultValidation, ValidateResultOptions,
74
74
  PhotoInput, PreparedImage, ImageSelector, VideoSaver, AlertFunction, FeatureUtilsConfig, VideoAlertFunction,
75
- MediaActionResult, MediaActionTranslations, ToastConfig,
76
75
  } from "./infrastructure/utils";
77
76
 
78
77
  export {
79
78
  useGeneration, usePendingJobs, useBackgroundGeneration,
80
79
  useGenerationFlow, useAIFeatureCallbacks,
81
80
  useAIGenerateState, AIGenerateStep,
82
- useGenerationOrchestrator, useImageGeneration, useVideoGeneration,
81
+ useGenerationOrchestrator, useImageGeneration, useVideoGeneration, useDualImageGeneration,
83
82
  createGenerationError, getAlertMessage, parseError,
84
83
  } from "./presentation/hooks";
85
84
 
@@ -92,6 +91,7 @@ export type {
92
91
  GenerationError, GenerationErrorType, AlertMessages, UseGenerationOrchestratorReturn,
93
92
  SingleImageInput, DualImageInput, ImageGenerationInput, ImageGenerationConfig,
94
93
  DualImageVideoInput, VideoGenerationConfig,
94
+ DualImageGenerationConfig, DualImageGenerationReturn,
95
95
  UploadedImage,
96
96
  } from "./presentation/hooks";
97
97
 
@@ -27,3 +27,8 @@ export type {
27
27
  VideoFeatureResult,
28
28
  VideoFeatureRequest,
29
29
  } from "./video-feature-executor.service";
30
+ export { executeMultiImageGeneration } from "./multi-image-generation.executor";
31
+ export type {
32
+ MultiImageGenerationInput,
33
+ MultiImageGenerationResult,
34
+ } from "./multi-image-generation.executor";
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Multi-Image Generation Executor
3
+ * Handles image generation with multiple input images (e.g., baby prediction)
4
+ * Sends image_urls array as required by FAL AI nano-banana/edit model
5
+ */
6
+
7
+ import { providerRegistry } from "./provider-registry.service";
8
+
9
+ declare const __DEV__: boolean;
10
+
11
+ /** Generation timeout in milliseconds (2 minutes) */
12
+ const GENERATION_TIMEOUT_MS = 120000;
13
+
14
+ /** Base64 image format prefix */
15
+ const BASE64_IMAGE_PREFIX = "data:image/jpeg;base64,";
16
+
17
+ /** Default model input values */
18
+ const MODEL_INPUT_DEFAULTS = {
19
+ aspectRatio: "1:1",
20
+ outputFormat: "jpeg",
21
+ numImages: 1,
22
+ enableSafetyChecker: false,
23
+ } as const;
24
+
25
+ export interface MultiImageGenerationInput {
26
+ /** Base64 encoded images */
27
+ readonly photos: readonly string[];
28
+ /** Complete prompt for generation */
29
+ readonly prompt: string;
30
+ /** AI model to use */
31
+ readonly model: string;
32
+ /** Aspect ratio (default: "1:1") */
33
+ readonly aspectRatio?: string;
34
+ /** Output format (default: "jpeg") */
35
+ readonly outputFormat?: string;
36
+ }
37
+
38
+ export interface MultiImageGenerationResult {
39
+ readonly success: boolean;
40
+ readonly imageUrl?: string;
41
+ readonly error?: string;
42
+ }
43
+
44
+ function formatBase64(base64: string): string {
45
+ return base64.startsWith("data:") ? base64 : `${BASE64_IMAGE_PREFIX}${base64}`;
46
+ }
47
+
48
+ /**
49
+ * Execute image generation with multiple input images
50
+ * Sends image_urls array as required by FAL AI API
51
+ */
52
+ export async function executeMultiImageGeneration(
53
+ input: MultiImageGenerationInput,
54
+ ): Promise<MultiImageGenerationResult> {
55
+ const provider = providerRegistry.getActiveProvider();
56
+ if (!provider?.isInitialized()) {
57
+ return { success: false, error: "AI provider not initialized" };
58
+ }
59
+
60
+ try {
61
+ const imageUrls = input.photos.map(formatBase64);
62
+
63
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
64
+ console.log("[MultiImageExecutor] Generation started", {
65
+ imageCount: imageUrls.length,
66
+ model: input.model,
67
+ });
68
+ }
69
+
70
+ const modelInput: Record<string, unknown> = {
71
+ prompt: input.prompt,
72
+ image_urls: imageUrls,
73
+ aspect_ratio: input.aspectRatio ?? MODEL_INPUT_DEFAULTS.aspectRatio,
74
+ output_format: input.outputFormat ?? MODEL_INPUT_DEFAULTS.outputFormat,
75
+ num_images: MODEL_INPUT_DEFAULTS.numImages,
76
+ enable_safety_checker: MODEL_INPUT_DEFAULTS.enableSafetyChecker,
77
+ };
78
+
79
+ const result = await provider.subscribe(input.model, modelInput, {
80
+ timeoutMs: GENERATION_TIMEOUT_MS,
81
+ });
82
+
83
+ const rawResult = result as Record<string, unknown>;
84
+ const data = (rawResult?.data ?? rawResult) as { images?: Array<{ url: string }> };
85
+ const imageUrl = data?.images?.[0]?.url;
86
+
87
+ return imageUrl
88
+ ? { success: true, imageUrl }
89
+ : { success: false, error: "No image generated" };
90
+ } catch (error) {
91
+ const message = error instanceof Error ? error.message : "Generation failed";
92
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
93
+ console.error("[MultiImageExecutor] Error:", message);
94
+ }
95
+ return { success: false, error: message };
96
+ }
97
+ }
@@ -12,5 +12,4 @@ export * from "./url-extractor.util";
12
12
  export * from "./photo-generation";
13
13
  export * from "./feature-utils";
14
14
  export * from "./video-helpers";
15
- export * from "./media-actions.util";
16
15
  export * from "./provider-validator.util";
@@ -9,6 +9,7 @@ export { useGenerationOrchestrator } from "./orchestrator";
9
9
  // Generic feature hooks
10
10
  export { useImageGeneration } from "./useImageGeneration";
11
11
  export { useVideoGeneration } from "./useVideoGeneration";
12
+ export { useDualImageGeneration } from "./useDualImageGeneration";
12
13
 
13
14
  // Types
14
15
  export type {
@@ -39,6 +40,11 @@ export type {
39
40
  VideoGenerationConfig,
40
41
  } from "./useVideoGeneration";
41
42
 
43
+ export type {
44
+ DualImageGenerationConfig,
45
+ DualImageGenerationReturn,
46
+ } from "./useDualImageGeneration";
47
+
42
48
  // Error utilities
43
49
  export {
44
50
  createGenerationError,
@@ -0,0 +1,209 @@
1
+ /**
2
+ * useDualImageGeneration Hook
3
+ * Generic hook for dual-image AI generation (e.g., baby prediction, couple futures)
4
+ * Sends image_urls array as required by FAL AI multi-image models
5
+ */
6
+
7
+ import { useState, useCallback, useMemo } from "react";
8
+ import {
9
+ MediaPickerService,
10
+ MediaQuality,
11
+ useAlert,
12
+ saveImageToGallery,
13
+ } from "@umituz/react-native-design-system";
14
+ import { useGenerationOrchestrator } from "./orchestrator";
15
+ import { executeMultiImageGeneration } from "../../../infrastructure/services/multi-image-generation.executor";
16
+ import { prepareImage } from "../../../infrastructure/utils/feature-utils";
17
+ import type { GenerationStrategy, AlertMessages } from "./types";
18
+
19
+ declare const __DEV__: boolean;
20
+
21
+ export interface DualImageGenerationConfig {
22
+ /** AI model to use */
23
+ readonly model: string;
24
+ /** Function that returns the prompt (can depend on external state) */
25
+ readonly getPrompt: () => string;
26
+ /** User ID for credit operations */
27
+ readonly userId: string | undefined;
28
+ /** Credit cost per generation */
29
+ readonly creditCost: number;
30
+ /** Alert messages */
31
+ readonly alertMessages: AlertMessages;
32
+ /** Image aspect ratio for picker */
33
+ readonly imageAspect?: [number, number];
34
+ /** Callbacks */
35
+ readonly onCreditsExhausted?: () => void;
36
+ readonly onSuccess?: (imageUrl: string) => void;
37
+ readonly onError?: (error: string) => void;
38
+ }
39
+
40
+ export interface DualImageGenerationReturn {
41
+ readonly sourceImageUri: string | null;
42
+ readonly targetImageUri: string | null;
43
+ readonly processedUrl: string | null;
44
+ readonly isProcessing: boolean;
45
+ readonly progress: number;
46
+ selectSourceImage(): Promise<void>;
47
+ selectTargetImage(): Promise<void>;
48
+ process(): Promise<void>;
49
+ save(): Promise<void>;
50
+ reset(): void;
51
+ }
52
+
53
+ interface GenerationInput {
54
+ sourceBase64: string;
55
+ targetBase64: string;
56
+ }
57
+
58
+ export const useDualImageGeneration = (
59
+ config: DualImageGenerationConfig,
60
+ ): DualImageGenerationReturn => {
61
+ const {
62
+ model,
63
+ getPrompt,
64
+ userId,
65
+ creditCost,
66
+ alertMessages,
67
+ imageAspect = [1, 1],
68
+ onCreditsExhausted,
69
+ onSuccess,
70
+ onError,
71
+ } = config;
72
+
73
+ const { showError, showSuccess } = useAlert();
74
+
75
+ // Image state
76
+ const [sourceImageUri, setSourceImageUri] = useState<string | null>(null);
77
+ const [targetImageUri, setTargetImageUri] = useState<string | null>(null);
78
+ const [sourceBase64, setSourceBase64] = useState<string | null>(null);
79
+ const [targetBase64, setTargetBase64] = useState<string | null>(null);
80
+ const [progress, setProgress] = useState(0);
81
+
82
+ // Generation strategy for orchestrator
83
+ const strategy: GenerationStrategy<GenerationInput, string> = useMemo(
84
+ () => ({
85
+ execute: async (input) => {
86
+ setProgress(30);
87
+ const result = await executeMultiImageGeneration({
88
+ photos: [input.sourceBase64, input.targetBase64],
89
+ prompt: getPrompt(),
90
+ model,
91
+ });
92
+ setProgress(90);
93
+
94
+ if (!result.success || !result.imageUrl) {
95
+ throw new Error(result.error || "Generation failed");
96
+ }
97
+
98
+ setProgress(100);
99
+ return result.imageUrl;
100
+ },
101
+ getCreditCost: () => creditCost,
102
+ }),
103
+ [model, getPrompt, creditCost],
104
+ );
105
+
106
+ // Use orchestrator for credit/error handling
107
+ const orchestrator = useGenerationOrchestrator(strategy, {
108
+ userId,
109
+ alertMessages,
110
+ onCreditsExhausted,
111
+ onSuccess: (result) => onSuccess?.(result as string),
112
+ onError: (error) => onError?.(error.message),
113
+ });
114
+
115
+ // Image selection handlers
116
+ const selectSourceImage = useCallback(async () => {
117
+ try {
118
+ const result = await MediaPickerService.pickSingleImage({
119
+ allowsEditing: true,
120
+ quality: MediaQuality.HIGH,
121
+ aspect: imageAspect,
122
+ });
123
+
124
+ if (!result.canceled && result.assets?.[0]) {
125
+ const asset = result.assets[0];
126
+ setSourceImageUri(asset.uri);
127
+ const base64 = await prepareImage(asset.uri);
128
+ setSourceBase64(base64);
129
+ }
130
+ } catch (error) {
131
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
132
+ console.error("[DualImageGeneration] Source image error:", error);
133
+ }
134
+ showError("Error", "Failed to select image");
135
+ }
136
+ }, [imageAspect, showError]);
137
+
138
+ const selectTargetImage = useCallback(async () => {
139
+ try {
140
+ const result = await MediaPickerService.pickSingleImage({
141
+ allowsEditing: true,
142
+ quality: MediaQuality.HIGH,
143
+ aspect: imageAspect,
144
+ });
145
+
146
+ if (!result.canceled && result.assets?.[0]) {
147
+ const asset = result.assets[0];
148
+ setTargetImageUri(asset.uri);
149
+ const base64 = await prepareImage(asset.uri);
150
+ setTargetBase64(base64);
151
+ }
152
+ } catch (error) {
153
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
154
+ console.error("[DualImageGeneration] Target image error:", error);
155
+ }
156
+ showError("Error", "Failed to select image");
157
+ }
158
+ }, [imageAspect, showError]);
159
+
160
+ // Process handler
161
+ const process = useCallback(async () => {
162
+ if (!sourceBase64 || !targetBase64) {
163
+ showError("Missing Photos", "Please upload both photos");
164
+ return;
165
+ }
166
+
167
+ setProgress(10);
168
+ try {
169
+ await orchestrator.generate({ sourceBase64, targetBase64 });
170
+ } catch {
171
+ setProgress(0);
172
+ }
173
+ }, [sourceBase64, targetBase64, orchestrator, showError]);
174
+
175
+ // Save handler - uses design-system saveImageToGallery
176
+ const save = useCallback(async () => {
177
+ if (!orchestrator.result) return;
178
+
179
+ const result = await saveImageToGallery(orchestrator.result);
180
+ if (result.success) {
181
+ showSuccess("Success", "Image saved to gallery");
182
+ } else {
183
+ showError("Error", result.error ?? "Failed to save");
184
+ }
185
+ }, [orchestrator.result, showSuccess, showError]);
186
+
187
+ // Reset handler
188
+ const reset = useCallback(() => {
189
+ setSourceImageUri(null);
190
+ setTargetImageUri(null);
191
+ setSourceBase64(null);
192
+ setTargetBase64(null);
193
+ setProgress(0);
194
+ orchestrator.reset();
195
+ }, [orchestrator]);
196
+
197
+ return {
198
+ sourceImageUri,
199
+ targetImageUri,
200
+ processedUrl: orchestrator.result,
201
+ isProcessing: orchestrator.isGenerating,
202
+ progress,
203
+ selectSourceImage,
204
+ selectTargetImage,
205
+ process,
206
+ save,
207
+ reset,
208
+ };
209
+ };
@@ -7,6 +7,7 @@ export {
7
7
  useGenerationOrchestrator,
8
8
  useImageGeneration,
9
9
  useVideoGeneration,
10
+ useDualImageGeneration,
10
11
  createGenerationError,
11
12
  getAlertMessage,
12
13
  parseError,
@@ -28,6 +29,8 @@ export type {
28
29
  ImageGenerationConfig,
29
30
  DualImageVideoInput,
30
31
  VideoGenerationConfig,
32
+ DualImageGenerationConfig,
33
+ DualImageGenerationReturn,
31
34
  } from "./generation";
32
35
 
33
36
  export { useGeneration } from "./use-generation";
@@ -1,200 +0,0 @@
1
- /**
2
- * Media Actions Utilities
3
- * Provides save to gallery and share functionality for generated media
4
- */
5
-
6
- declare const __DEV__: boolean;
7
-
8
- export interface MediaActionResult {
9
- readonly success: boolean;
10
- readonly error?: string;
11
- }
12
-
13
- export interface MediaActionTranslations {
14
- readonly success?: string;
15
- readonly error?: string;
16
- readonly permissionDenied?: string;
17
- readonly saveFailed?: string;
18
- readonly shareFailed?: string;
19
- readonly shareNotAvailable?: string;
20
- }
21
-
22
- export interface ToastConfig {
23
- readonly show: (config: {
24
- type: "success" | "error";
25
- text1: string;
26
- text2?: string;
27
- }) => void;
28
- }
29
-
30
- /**
31
- * Downloads a file from URL to local storage
32
- * @param uri - URL or local path
33
- * @param prefix - File name prefix
34
- * @returns Local file path
35
- */
36
- const downloadToLocal = async (
37
- uri: string,
38
- prefix: string = "media"
39
- ): Promise<string> => {
40
- if (!uri.startsWith("http")) {
41
- return uri;
42
- }
43
-
44
- const FileSystem = require("expo-file-system");
45
- const fileExt = uri.split(".").pop()?.split("?")[0] || "jpg";
46
- const fileRef =
47
- FileSystem.documentDirectory + `${prefix}_${Date.now()}.${fileExt}`;
48
- const { uri: downloadedUri } = await FileSystem.downloadAsync(uri, fileRef);
49
- return downloadedUri;
50
- };
51
-
52
- /**
53
- * Saves media to device gallery
54
- * @param uri - URL or local path of the media
55
- * @param translations - Optional translated messages
56
- * @param toast - Optional toast instance for notifications
57
- * @returns Result with success status
58
- */
59
- export const saveMediaToGallery = async (
60
- uri: string,
61
- translations?: MediaActionTranslations,
62
- toast?: ToastConfig
63
- ): Promise<MediaActionResult> => {
64
- try {
65
- const MediaLibrary = require("expo-media-library");
66
-
67
- const { status } = await MediaLibrary.requestPermissionsAsync();
68
- if (status !== "granted") {
69
- const errorMsg = translations?.permissionDenied || "Permission denied";
70
- toast?.show({
71
- type: "error",
72
- text1: translations?.error || "Error",
73
- text2: errorMsg,
74
- });
75
- return { success: false, error: errorMsg };
76
- }
77
-
78
- const localUri = await downloadToLocal(uri, "download");
79
- await MediaLibrary.saveToLibraryAsync(localUri);
80
-
81
- toast?.show({
82
- type: "success",
83
- text1: translations?.success || "Success",
84
- text2: "Saved to gallery",
85
- });
86
-
87
- return { success: true };
88
- } catch (error) {
89
- // Debug logging in development
90
- if (typeof __DEV__ !== "undefined" && __DEV__) {
91
- console.error("[MediaActions] Save failed:", error);
92
- }
93
- const errorMsg = translations?.saveFailed || "Failed to save media";
94
- toast?.show({
95
- type: "error",
96
- text1: translations?.error || "Error",
97
- text2: errorMsg,
98
- });
99
- return {
100
- success: false,
101
- error: error instanceof Error ? error.message : errorMsg,
102
- };
103
- }
104
- };
105
-
106
- /**
107
- * Shares media using device share sheet
108
- * @param uri - URL or local path of the media
109
- * @param translations - Optional translated messages
110
- * @param toast - Optional toast instance for notifications
111
- * @returns Result with success status
112
- */
113
- export const shareMedia = async (
114
- uri: string,
115
- translations?: MediaActionTranslations,
116
- toast?: ToastConfig
117
- ): Promise<MediaActionResult> => {
118
- try {
119
- const Sharing = require("expo-sharing");
120
-
121
- const isAvailable = await Sharing.isAvailableAsync();
122
- if (!isAvailable) {
123
- const errorMsg =
124
- translations?.shareNotAvailable ||
125
- "Sharing is not available on this device";
126
- toast?.show({
127
- type: "error",
128
- text1: translations?.error || "Error",
129
- text2: errorMsg,
130
- });
131
- return { success: false, error: errorMsg };
132
- }
133
-
134
- const localUri = await downloadToLocal(uri, "share");
135
- await Sharing.shareAsync(localUri);
136
-
137
- return { success: true };
138
- } catch (error) {
139
- // Debug logging in development
140
- if (typeof __DEV__ !== "undefined" && __DEV__) {
141
- console.error("[MediaActions] Share failed:", error);
142
- }
143
- const errorMsg = translations?.shareFailed || "Failed to share media";
144
- toast?.show({
145
- type: "error",
146
- text1: translations?.error || "Error",
147
- text2: errorMsg,
148
- });
149
- return {
150
- success: false,
151
- error: error instanceof Error ? error.message : errorMsg,
152
- };
153
- }
154
- };
155
-
156
- /**
157
- * Creates save handler for use in wizard flows
158
- * @param translations - Translated messages
159
- * @param toast - Toast instance for notifications
160
- * @returns Save handler function
161
- */
162
- export const createSaveHandler = (
163
- translations?: MediaActionTranslations,
164
- toast?: ToastConfig
165
- ) => {
166
- return async (uri: string): Promise<void> => {
167
- await saveMediaToGallery(uri, translations, toast);
168
- };
169
- };
170
-
171
- /**
172
- * Creates share handler for use in wizard flows
173
- * @param translations - Translated messages
174
- * @param toast - Toast instance for notifications
175
- * @returns Share handler function
176
- */
177
- export const createShareHandler = (
178
- translations?: MediaActionTranslations,
179
- toast?: ToastConfig
180
- ) => {
181
- return async (uri: string): Promise<void> => {
182
- await shareMedia(uri, translations, toast);
183
- };
184
- };
185
-
186
- /**
187
- * Creates both save and share handlers
188
- * @param translations - Translated messages
189
- * @param toast - Toast instance for notifications
190
- * @returns Object with onSave and onShare handlers
191
- */
192
- export const createMediaHandlers = (
193
- translations?: MediaActionTranslations,
194
- toast?: ToastConfig
195
- ) => {
196
- return {
197
- onSave: createSaveHandler(translations, toast),
198
- onShare: createShareHandler(translations, toast),
199
- };
200
- };