@umituz/react-native-ai-generation-content 1.27.13 → 1.27.15
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/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.ts +131 -20
- package/src/features/text-to-image/index.ts +0 -7
- package/src/features/text-to-image/presentation/hooks/index.ts +0 -7
- package/src/infrastructure/utils/feature-utils.ts +1 -6
- package/src/features/image-to-video/presentation/hooks/useGenerationExecution.ts +0 -143
- package/src/features/image-to-video/presentation/hooks/useImageToVideoValidation.ts +0 -46
- package/src/features/text-to-image/presentation/hooks/useTextToImageFeature.ts +0 -111
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.15",
|
|
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 },
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Image-to-Video Feature Hook
|
|
3
|
-
*
|
|
3
|
+
* Uses centralized useGenerationOrchestrator for consistent auth, credits, and error handling
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useState, useCallback, useMemo } from "react";
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
useGenerationOrchestrator,
|
|
9
|
+
type GenerationStrategy,
|
|
10
|
+
type AlertMessages,
|
|
11
|
+
} from "../../../../presentation/hooks/generation";
|
|
12
|
+
import { executeImageToVideo } from "../../infrastructure/services";
|
|
9
13
|
import type {
|
|
10
14
|
ImageToVideoFeatureState,
|
|
11
15
|
ImageToVideoFeatureConfig,
|
|
@@ -16,7 +20,6 @@ import type {
|
|
|
16
20
|
|
|
17
21
|
declare const __DEV__: boolean;
|
|
18
22
|
|
|
19
|
-
// Initial state (inlined from constants file)
|
|
20
23
|
const INITIAL_STATE: ImageToVideoFeatureState = {
|
|
21
24
|
imageUri: null,
|
|
22
25
|
motionPrompt: "",
|
|
@@ -27,7 +30,14 @@ const INITIAL_STATE: ImageToVideoFeatureState = {
|
|
|
27
30
|
error: null,
|
|
28
31
|
};
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
const DEFAULT_ALERT_MESSAGES: AlertMessages = {
|
|
34
|
+
networkError: "No internet connection. Please check your network.",
|
|
35
|
+
policyViolation: "Content not allowed. Please try again.",
|
|
36
|
+
saveFailed: "Failed to save. Please try again.",
|
|
37
|
+
creditFailed: "Credit operation failed. Please try again.",
|
|
38
|
+
unknown: "An error occurred. Please try again.",
|
|
39
|
+
};
|
|
40
|
+
|
|
31
41
|
export interface UseImageToVideoFeatureProps {
|
|
32
42
|
config: ImageToVideoFeatureConfig;
|
|
33
43
|
callbacks?: ImageToVideoFeatureCallbacks;
|
|
@@ -44,15 +54,99 @@ export interface UseImageToVideoFeatureReturn {
|
|
|
44
54
|
canGenerate: boolean;
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
interface VideoGenerationInput {
|
|
58
|
+
imageUri: string;
|
|
59
|
+
imageBase64: string;
|
|
60
|
+
motionPrompt: string;
|
|
61
|
+
options?: Omit<ImageToVideoGenerateParams, "imageUri" | "motionPrompt">;
|
|
62
|
+
creationId: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function generateCreationId(): string {
|
|
66
|
+
return `image-to-video_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
47
69
|
export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseImageToVideoFeatureReturn {
|
|
48
70
|
const { config, callbacks, userId } = props;
|
|
49
71
|
const [state, setState] = useState<ImageToVideoFeatureState>(INITIAL_STATE);
|
|
50
72
|
|
|
51
|
-
const
|
|
73
|
+
const strategy: GenerationStrategy<VideoGenerationInput, ImageToVideoResult> = useMemo(
|
|
74
|
+
() => ({
|
|
75
|
+
execute: async (input, onProgress) => {
|
|
76
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
77
|
+
console.log("[ImageToVideo] Executing generation, creationId:", input.creationId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
config.onProcessingStart?.();
|
|
81
|
+
|
|
82
|
+
callbacks?.onGenerationStart?.({
|
|
83
|
+
creationId: input.creationId,
|
|
84
|
+
type: "image-to-video",
|
|
85
|
+
imageUri: input.imageUri,
|
|
86
|
+
metadata: input.options as Record<string, unknown> | undefined,
|
|
87
|
+
}).catch(() => {});
|
|
88
|
+
|
|
89
|
+
const result = await executeImageToVideo(
|
|
90
|
+
{
|
|
91
|
+
imageUri: input.imageUri,
|
|
92
|
+
imageBase64: input.imageBase64,
|
|
93
|
+
userId,
|
|
94
|
+
motionPrompt: input.motionPrompt || undefined,
|
|
95
|
+
options: input.options,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
model: config.model,
|
|
99
|
+
buildInput: config.buildInput,
|
|
100
|
+
extractResult: config.extractResult,
|
|
101
|
+
onProgress: (progress) => {
|
|
102
|
+
setState((prev) => ({ ...prev, progress }));
|
|
103
|
+
onProgress?.(progress);
|
|
104
|
+
callbacks?.onProgress?.(progress);
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (!result.success || !result.videoUrl) {
|
|
110
|
+
throw new Error(result.error || "Generation failed");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setState((prev) => ({
|
|
114
|
+
...prev,
|
|
115
|
+
videoUrl: result.videoUrl ?? null,
|
|
116
|
+
thumbnailUrl: result.thumbnailUrl ?? null,
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
return result;
|
|
120
|
+
},
|
|
121
|
+
getCreditCost: () => config.creditCost ?? 0,
|
|
122
|
+
save: async (result) => {
|
|
123
|
+
if (result.success && result.videoUrl && state.imageUri) {
|
|
124
|
+
await callbacks?.onCreationSave?.({
|
|
125
|
+
creationId: generateCreationId(),
|
|
126
|
+
type: "image-to-video",
|
|
127
|
+
videoUrl: result.videoUrl,
|
|
128
|
+
thumbnailUrl: result.thumbnailUrl,
|
|
129
|
+
imageUri: state.imageUri,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
[config, callbacks, userId, state.imageUri],
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const orchestrator = useGenerationOrchestrator(strategy, {
|
|
52
138
|
userId,
|
|
53
|
-
|
|
54
|
-
callbacks,
|
|
55
|
-
|
|
139
|
+
alertMessages: DEFAULT_ALERT_MESSAGES,
|
|
140
|
+
onCreditsExhausted: () => callbacks?.onShowPaywall?.(config.creditCost ?? 0),
|
|
141
|
+
onSuccess: (result) => {
|
|
142
|
+
const videoResult = result as ImageToVideoResult;
|
|
143
|
+
callbacks?.onGenerate?.(videoResult);
|
|
144
|
+
config.onProcessingComplete?.(videoResult);
|
|
145
|
+
},
|
|
146
|
+
onError: (err) => {
|
|
147
|
+
callbacks?.onError?.(err.message);
|
|
148
|
+
config.onError?.(err.message);
|
|
149
|
+
},
|
|
56
150
|
});
|
|
57
151
|
|
|
58
152
|
const setImageUri = useCallback(
|
|
@@ -77,23 +171,40 @@ export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseI
|
|
|
77
171
|
console.log("[ImageToVideoFeature] generate called, hasImage:", !!effectiveImageUri);
|
|
78
172
|
}
|
|
79
173
|
|
|
174
|
+
if (!effectiveImageUri) {
|
|
175
|
+
const error = "Image is required";
|
|
176
|
+
setState((prev) => ({ ...prev, error }));
|
|
177
|
+
callbacks?.onError?.(error);
|
|
178
|
+
return { success: false, error };
|
|
179
|
+
}
|
|
180
|
+
|
|
80
181
|
if (paramImageUri) {
|
|
81
182
|
setState((prev) => ({ ...prev, imageUri: paramImageUri }));
|
|
82
183
|
}
|
|
83
184
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
config.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
185
|
+
setState((prev) => ({ ...prev, isProcessing: true, error: null, progress: 0 }));
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const imageBase64 = await config.prepareImage(effectiveImageUri);
|
|
189
|
+
|
|
190
|
+
const input: VideoGenerationInput = {
|
|
191
|
+
imageUri: effectiveImageUri,
|
|
192
|
+
imageBase64,
|
|
193
|
+
motionPrompt: effectiveMotionPrompt,
|
|
194
|
+
options,
|
|
195
|
+
creationId: generateCreationId(),
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
await orchestrator.generate(input);
|
|
199
|
+
setState((prev) => ({ ...prev, isProcessing: false }));
|
|
200
|
+
return { success: true, videoUrl: state.videoUrl || undefined };
|
|
201
|
+
} catch (error) {
|
|
202
|
+
const message = error instanceof Error ? error.message : "Generation failed";
|
|
203
|
+
setState((prev) => ({ ...prev, isProcessing: false, error: message }));
|
|
204
|
+
return { success: false, error: message };
|
|
92
205
|
}
|
|
93
|
-
|
|
94
|
-
return executeGeneration(effectiveImageUri!, effectiveMotionPrompt, options);
|
|
95
206
|
},
|
|
96
|
-
[state.imageUri, state.motionPrompt,
|
|
207
|
+
[state.imageUri, state.motionPrompt, state.videoUrl, config, callbacks, orchestrator],
|
|
97
208
|
);
|
|
98
209
|
|
|
99
210
|
const reset = useCallback(() => {
|
|
@@ -83,13 +83,6 @@ export type {
|
|
|
83
83
|
UseTextToImageFormReturn,
|
|
84
84
|
} from "./presentation";
|
|
85
85
|
|
|
86
|
-
// Provider-based Feature Hook
|
|
87
|
-
export { useTextToImageFeature } from "./presentation";
|
|
88
|
-
export type {
|
|
89
|
-
UseTextToImageFeatureProps,
|
|
90
|
-
UseTextToImageFeatureReturn,
|
|
91
|
-
} from "./presentation";
|
|
92
|
-
|
|
93
86
|
// =============================================================================
|
|
94
87
|
// PRESENTATION LAYER - Components
|
|
95
88
|
// =============================================================================
|
|
@@ -21,10 +21,3 @@ export type {
|
|
|
21
21
|
UseTextToImageFormOptions,
|
|
22
22
|
UseTextToImageFormReturn,
|
|
23
23
|
} from "./useTextToImageForm";
|
|
24
|
-
|
|
25
|
-
// Provider-based Feature Hook
|
|
26
|
-
export { useTextToImageFeature } from "./useTextToImageFeature";
|
|
27
|
-
export type {
|
|
28
|
-
UseTextToImageFeatureProps,
|
|
29
|
-
UseTextToImageFeatureReturn,
|
|
30
|
-
} from "./useTextToImageFeature";
|
|
@@ -3,14 +3,9 @@
|
|
|
3
3
|
* Uses ONLY configured app services - no alternatives
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import { readFileAsBase64 } from "@umituz/react-native-design-system";
|
|
7
7
|
import { getAuthService, getCreditService, getPaywallService, isAppServicesConfigured } from "../config/app-services.config";
|
|
8
8
|
|
|
9
|
-
async function readFileAsBase64(uri: string): Promise<string> {
|
|
10
|
-
const base64 = await FileSystem.readAsStringAsync(uri, { encoding: "base64" });
|
|
11
|
-
return `data:image/jpeg;base64,${base64}`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
9
|
declare const __DEV__: boolean;
|
|
15
10
|
|
|
16
11
|
export type ImageSelector = () => Promise<string | null>;
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useGenerationExecution Hook
|
|
3
|
-
* Handles the core generation execution logic for image-to-video
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useCallback } from "react";
|
|
7
|
-
import { executeImageToVideo } from "../../infrastructure/services";
|
|
8
|
-
import type {
|
|
9
|
-
ImageToVideoFeatureConfig,
|
|
10
|
-
ImageToVideoFeatureCallbacks,
|
|
11
|
-
ImageToVideoResult,
|
|
12
|
-
ImageToVideoGenerateParams,
|
|
13
|
-
ImageToVideoFeatureState,
|
|
14
|
-
} from "../../domain/types";
|
|
15
|
-
|
|
16
|
-
declare const __DEV__: boolean;
|
|
17
|
-
|
|
18
|
-
interface UseGenerationExecutionParams {
|
|
19
|
-
userId: string;
|
|
20
|
-
config: ImageToVideoFeatureConfig;
|
|
21
|
-
callbacks?: ImageToVideoFeatureCallbacks;
|
|
22
|
-
setState: React.Dispatch<React.SetStateAction<ImageToVideoFeatureState>>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function useGenerationExecution({
|
|
26
|
-
userId,
|
|
27
|
-
config,
|
|
28
|
-
callbacks,
|
|
29
|
-
setState,
|
|
30
|
-
}: UseGenerationExecutionParams) {
|
|
31
|
-
return useCallback(
|
|
32
|
-
async (
|
|
33
|
-
imageUri: string,
|
|
34
|
-
motionPrompt: string,
|
|
35
|
-
options?: Omit<ImageToVideoGenerateParams, "imageUri" | "motionPrompt">,
|
|
36
|
-
): Promise<ImageToVideoResult> => {
|
|
37
|
-
const creationId = `image-to-video_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
38
|
-
|
|
39
|
-
setState((prev) => ({
|
|
40
|
-
...prev,
|
|
41
|
-
imageUri,
|
|
42
|
-
isProcessing: true,
|
|
43
|
-
progress: 0,
|
|
44
|
-
error: null,
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
48
|
-
console.log("[ImageToVideoFeature] Starting generation, creationId:", creationId);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
config.onProcessingStart?.();
|
|
52
|
-
|
|
53
|
-
if (callbacks?.onGenerationStart) {
|
|
54
|
-
callbacks.onGenerationStart({
|
|
55
|
-
creationId,
|
|
56
|
-
type: "image-to-video",
|
|
57
|
-
imageUri,
|
|
58
|
-
metadata: options as Record<string, unknown> | undefined,
|
|
59
|
-
}).catch((err) => {
|
|
60
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
61
|
-
console.warn("[ImageToVideoFeature] onGenerationStart failed:", err);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const imageBase64 = await config.prepareImage(imageUri);
|
|
68
|
-
|
|
69
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
70
|
-
console.log("[ImageToVideoFeature] Image prepared, calling executeImageToVideo");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const result = await executeImageToVideo(
|
|
74
|
-
{
|
|
75
|
-
imageUri,
|
|
76
|
-
imageBase64,
|
|
77
|
-
userId,
|
|
78
|
-
motionPrompt: motionPrompt || undefined,
|
|
79
|
-
options,
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
model: config.model,
|
|
83
|
-
buildInput: config.buildInput,
|
|
84
|
-
extractResult: config.extractResult,
|
|
85
|
-
onProgress: (progress) => {
|
|
86
|
-
setState((prev) => ({ ...prev, progress }));
|
|
87
|
-
callbacks?.onProgress?.(progress);
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
if (result.success && result.videoUrl) {
|
|
93
|
-
setState((prev) => ({
|
|
94
|
-
...prev,
|
|
95
|
-
videoUrl: result.videoUrl ?? null,
|
|
96
|
-
thumbnailUrl: result.thumbnailUrl ?? null,
|
|
97
|
-
isProcessing: false,
|
|
98
|
-
progress: 100,
|
|
99
|
-
}));
|
|
100
|
-
|
|
101
|
-
if (callbacks?.onCreditDeduct && config.creditCost) {
|
|
102
|
-
await callbacks.onCreditDeduct(config.creditCost);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (callbacks?.onCreationSave) {
|
|
106
|
-
await callbacks.onCreationSave({
|
|
107
|
-
creationId,
|
|
108
|
-
type: "image-to-video",
|
|
109
|
-
videoUrl: result.videoUrl,
|
|
110
|
-
thumbnailUrl: result.thumbnailUrl,
|
|
111
|
-
imageUri,
|
|
112
|
-
metadata: options as Record<string, unknown> | undefined,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
callbacks?.onGenerate?.(result);
|
|
117
|
-
} else {
|
|
118
|
-
const error = result.error || "Generation failed";
|
|
119
|
-
setState((prev) => ({ ...prev, isProcessing: false, error }));
|
|
120
|
-
config.onError?.(error);
|
|
121
|
-
callbacks?.onError?.(error);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
config.onProcessingComplete?.(result);
|
|
125
|
-
return result;
|
|
126
|
-
} catch (err) {
|
|
127
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
128
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
129
|
-
console.error("[ImageToVideoFeature] Generation error:", errorMessage);
|
|
130
|
-
}
|
|
131
|
-
setState((prev) => ({
|
|
132
|
-
...prev,
|
|
133
|
-
isProcessing: false,
|
|
134
|
-
error: errorMessage,
|
|
135
|
-
}));
|
|
136
|
-
config.onError?.(errorMessage);
|
|
137
|
-
callbacks?.onError?.(errorMessage);
|
|
138
|
-
return { success: false, error: errorMessage };
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
[userId, config, callbacks],
|
|
142
|
-
);
|
|
143
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image-to-Video Validation Utilities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { ImageToVideoFeatureCallbacks, ImageToVideoResult } from "../../domain/types";
|
|
6
|
-
|
|
7
|
-
declare const __DEV__: boolean;
|
|
8
|
-
|
|
9
|
-
export interface ValidationResult extends ImageToVideoResult {
|
|
10
|
-
shouldProceed: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function validateImageToVideoGeneration(
|
|
14
|
-
effectiveImageUri: string | null,
|
|
15
|
-
callbacks?: ImageToVideoFeatureCallbacks,
|
|
16
|
-
creditCost?: number,
|
|
17
|
-
): Promise<ValidationResult> {
|
|
18
|
-
if (!effectiveImageUri) {
|
|
19
|
-
const error = "Image is required";
|
|
20
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
21
|
-
console.log("[ImageToVideoFeature] Generate failed: Image is required");
|
|
22
|
-
}
|
|
23
|
-
callbacks?.onError?.(error);
|
|
24
|
-
return { success: false, error, shouldProceed: false };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (callbacks?.onAuthCheck && !callbacks.onAuthCheck()) {
|
|
28
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
29
|
-
console.log("[ImageToVideoFeature] Generate failed: Authentication required");
|
|
30
|
-
}
|
|
31
|
-
return { success: false, error: "Authentication required", shouldProceed: false };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (callbacks?.onCreditCheck && creditCost) {
|
|
35
|
-
const hasCredits = await callbacks.onCreditCheck(creditCost);
|
|
36
|
-
if (!hasCredits) {
|
|
37
|
-
callbacks?.onShowPaywall?.(creditCost);
|
|
38
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
39
|
-
console.log("[ImageToVideoFeature] Generate failed: Insufficient credits");
|
|
40
|
-
}
|
|
41
|
-
return { success: false, error: "Insufficient credits", shouldProceed: false };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return { success: true, shouldProceed: true };
|
|
46
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Text-to-Image Feature Hook
|
|
3
|
-
* Provider-agnostic hook for text-to-image generation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useCallback } from "react";
|
|
7
|
-
import { executeTextToImage } from "../../infrastructure/services";
|
|
8
|
-
import type {
|
|
9
|
-
TextToImageFeatureState,
|
|
10
|
-
TextToImageFeatureConfig,
|
|
11
|
-
TextToImageResult,
|
|
12
|
-
TextToImageOptions,
|
|
13
|
-
} from "../../domain/types";
|
|
14
|
-
|
|
15
|
-
export interface UseTextToImageFeatureProps {
|
|
16
|
-
config: TextToImageFeatureConfig;
|
|
17
|
-
userId: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface UseTextToImageFeatureReturn {
|
|
21
|
-
state: TextToImageFeatureState;
|
|
22
|
-
setPrompt: (prompt: string) => void;
|
|
23
|
-
generate: (options?: TextToImageOptions) => Promise<TextToImageResult>;
|
|
24
|
-
reset: () => void;
|
|
25
|
-
isReady: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const initialState: TextToImageFeatureState = {
|
|
29
|
-
prompt: "",
|
|
30
|
-
imageUrl: null,
|
|
31
|
-
imageUrls: [],
|
|
32
|
-
isProcessing: false,
|
|
33
|
-
progress: 0,
|
|
34
|
-
error: null,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export function useTextToImageFeature(
|
|
38
|
-
props: UseTextToImageFeatureProps,
|
|
39
|
-
): UseTextToImageFeatureReturn {
|
|
40
|
-
const { config, userId } = props;
|
|
41
|
-
const [state, setState] = useState<TextToImageFeatureState>(initialState);
|
|
42
|
-
|
|
43
|
-
const setPrompt = useCallback(
|
|
44
|
-
(prompt: string) => {
|
|
45
|
-
setState((prev) => ({ ...prev, prompt, error: null }));
|
|
46
|
-
config.onPromptChange?.(prompt);
|
|
47
|
-
},
|
|
48
|
-
[config],
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const generate = useCallback(
|
|
52
|
-
async (options?: TextToImageOptions): Promise<TextToImageResult> => {
|
|
53
|
-
if (!state.prompt) {
|
|
54
|
-
const error = "Prompt is required";
|
|
55
|
-
setState((prev) => ({ ...prev, error }));
|
|
56
|
-
return { success: false, error };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
setState((prev) => ({
|
|
60
|
-
...prev,
|
|
61
|
-
isProcessing: true,
|
|
62
|
-
progress: 0,
|
|
63
|
-
error: null,
|
|
64
|
-
}));
|
|
65
|
-
|
|
66
|
-
config.onProcessingStart?.();
|
|
67
|
-
|
|
68
|
-
const result = await executeTextToImage(
|
|
69
|
-
{ prompt: state.prompt, userId, options },
|
|
70
|
-
{
|
|
71
|
-
model: config.model,
|
|
72
|
-
buildInput: config.buildInput,
|
|
73
|
-
extractResult: config.extractResult,
|
|
74
|
-
onProgress: (progress) => {
|
|
75
|
-
setState((prev) => ({ ...prev, progress }));
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
if (result.success && result.imageUrl) {
|
|
81
|
-
setState((prev) => ({
|
|
82
|
-
...prev,
|
|
83
|
-
imageUrl: result.imageUrl ?? null,
|
|
84
|
-
imageUrls: result.imageUrls ?? [],
|
|
85
|
-
isProcessing: false,
|
|
86
|
-
progress: 100,
|
|
87
|
-
}));
|
|
88
|
-
} else {
|
|
89
|
-
const error = result.error || "Generation failed";
|
|
90
|
-
setState((prev) => ({
|
|
91
|
-
...prev,
|
|
92
|
-
isProcessing: false,
|
|
93
|
-
error,
|
|
94
|
-
}));
|
|
95
|
-
config.onError?.(error);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
config.onProcessingComplete?.(result);
|
|
99
|
-
return result;
|
|
100
|
-
},
|
|
101
|
-
[state.prompt, userId, config],
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
const reset = useCallback(() => {
|
|
105
|
-
setState(initialState);
|
|
106
|
-
}, []);
|
|
107
|
-
|
|
108
|
-
const isReady = state.prompt.length > 0 && !state.isProcessing;
|
|
109
|
-
|
|
110
|
-
return { state, setPrompt, generate, reset, isReady };
|
|
111
|
-
}
|