@umituz/react-native-ai-generation-content 1.61.2 → 1.61.4
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/core/types/provider.types.ts +1 -10
- package/src/index.ts +4 -2
- package/src/infrastructure/services/index.ts +5 -0
- package/src/infrastructure/services/multi-image-generation.executor.ts +97 -0
- package/src/presentation/hooks/generation/index.ts +6 -0
- package/src/presentation/hooks/generation/useDualImageGeneration.ts +209 -0
- package/src/presentation/hooks/index.ts +3 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.61.
|
|
3
|
+
"version": "1.61.4",
|
|
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",
|
|
@@ -1,17 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AI Provider Types
|
|
3
|
-
* Core interfaces for AI generation providers
|
|
4
|
-
*
|
|
5
|
-
* @module @umituz/react-native-ai-generation-content/core
|
|
2
|
+
* AI Provider Types - Core interfaces for AI generation providers
|
|
6
3
|
*/
|
|
7
4
|
|
|
8
|
-
// =============================================================================
|
|
9
5
|
// Feature Types
|
|
10
|
-
// =============================================================================
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Feature types for image processing (output: image)
|
|
14
|
-
*/
|
|
15
6
|
export type ImageFeatureType =
|
|
16
7
|
| "upscale"
|
|
17
8
|
| "photo-restore"
|
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 {
|
|
@@ -79,7 +80,7 @@ export {
|
|
|
79
80
|
useGeneration, usePendingJobs, useBackgroundGeneration,
|
|
80
81
|
useGenerationFlow, useAIFeatureCallbacks,
|
|
81
82
|
useAIGenerateState, AIGenerateStep,
|
|
82
|
-
useGenerationOrchestrator, useImageGeneration, useVideoGeneration,
|
|
83
|
+
useGenerationOrchestrator, useImageGeneration, useVideoGeneration, useDualImageGeneration,
|
|
83
84
|
createGenerationError, getAlertMessage, parseError,
|
|
84
85
|
} from "./presentation/hooks";
|
|
85
86
|
|
|
@@ -92,6 +93,7 @@ export type {
|
|
|
92
93
|
GenerationError, GenerationErrorType, AlertMessages, UseGenerationOrchestratorReturn,
|
|
93
94
|
SingleImageInput, DualImageInput, ImageGenerationInput, ImageGenerationConfig,
|
|
94
95
|
DualImageVideoInput, VideoGenerationConfig,
|
|
96
|
+
DualImageGenerationConfig, DualImageGenerationReturn,
|
|
95
97
|
UploadedImage,
|
|
96
98
|
} from "./presentation/hooks";
|
|
97
99
|
|
|
@@ -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
|
+
}
|
|
@@ -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
|
+
} from "@umituz/react-native-design-system";
|
|
13
|
+
import { useGenerationOrchestrator } from "./orchestrator";
|
|
14
|
+
import { executeMultiImageGeneration } from "../../../infrastructure/services/multi-image-generation.executor";
|
|
15
|
+
import { prepareImage } from "../../../infrastructure/utils/feature-utils";
|
|
16
|
+
import { saveMediaToGallery } from "../../../infrastructure/utils/media-actions.util";
|
|
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
|
|
176
|
+
const save = useCallback(async () => {
|
|
177
|
+
if (!orchestrator.result) return;
|
|
178
|
+
|
|
179
|
+
const result = await saveMediaToGallery(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";
|