@umituz/react-native-ai-generation-content 1.35.9 → 1.36.0

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.35.9",
3
+ "version": "1.36.0",
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",
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Image Generation Executor
3
+ * Handles the actual image generation execution
4
+ */
5
+
6
+ import { buildFacePreservationPrompt } from "../../../../prompts/infrastructure/builders/face-preservation-builder";
7
+ import { buildInteractionStylePrompt } from "../../../../prompts/infrastructure/builders/interaction-style-builder";
8
+ import type { ImageGenerationInput } from "./image-generation.types";
9
+ import {
10
+ GENERATION_TIMEOUT_MS,
11
+ BASE64_IMAGE_PREFIX,
12
+ DEFAULT_STYLE_VALUE,
13
+ MODEL_INPUT_DEFAULTS,
14
+ } from "./wizard-strategy.constants";
15
+
16
+ declare const __DEV__: boolean;
17
+
18
+ interface ExecutionResult {
19
+ success: boolean;
20
+ imageUrl?: string;
21
+ error?: string;
22
+ }
23
+
24
+ /**
25
+ * Formats base64 string with proper data URI prefix
26
+ */
27
+ function formatBase64(base64: string): string {
28
+ return base64.startsWith("data:") ? base64 : `${BASE64_IMAGE_PREFIX}${base64}`;
29
+ }
30
+
31
+ /**
32
+ * Builds the final prompt based on input type (photo-based or text-to-image)
33
+ */
34
+ function buildFinalPrompt(input: ImageGenerationInput, imageUrls: string[]): string {
35
+ const hasPhotos = imageUrls.length > 0;
36
+
37
+ if (hasPhotos) {
38
+ const facePrompt = buildFacePreservationPrompt({
39
+ scenarioPrompt: input.prompt,
40
+ personCount: imageUrls.length,
41
+ });
42
+
43
+ const interactionPrompt = buildInteractionStylePrompt({
44
+ style: input.interactionStyle ?? "romantic",
45
+ personCount: imageUrls.length,
46
+ });
47
+
48
+ return interactionPrompt ? `${facePrompt}\n\n${interactionPrompt}` : facePrompt;
49
+ }
50
+
51
+ // Text-to-image with optional style
52
+ if (input.style && input.style !== DEFAULT_STYLE_VALUE) {
53
+ return `${input.prompt}. Style: ${input.style}`;
54
+ }
55
+
56
+ return input.prompt;
57
+ }
58
+
59
+ /**
60
+ * Executes image generation using the AI provider
61
+ */
62
+ export async function executeImageGeneration(
63
+ input: ImageGenerationInput,
64
+ model: string,
65
+ onProgress?: (progress: number) => void,
66
+ ): Promise<ExecutionResult> {
67
+ const { providerRegistry } = await import("../../../../../infrastructure/services/provider-registry.service");
68
+
69
+ const provider = providerRegistry.getActiveProvider();
70
+ if (!provider?.isInitialized()) {
71
+ return { success: false, error: "AI provider not initialized" };
72
+ }
73
+
74
+ try {
75
+ const imageUrls = input.photos.map(formatBase64);
76
+ const finalPrompt = buildFinalPrompt(input, imageUrls);
77
+
78
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
79
+ const mode = imageUrls.length > 0 ? "Photo-based" : "Text-to-image";
80
+ console.log(`[ImageExecutor] ${mode} generation`, { personCount: imageUrls.length });
81
+ }
82
+
83
+ const modelInput: Record<string, unknown> = {
84
+ prompt: finalPrompt,
85
+ aspect_ratio: MODEL_INPUT_DEFAULTS.aspectRatio,
86
+ output_format: MODEL_INPUT_DEFAULTS.outputFormat,
87
+ num_images: MODEL_INPUT_DEFAULTS.numImages,
88
+ enable_safety_checker: MODEL_INPUT_DEFAULTS.enableSafetyChecker,
89
+ };
90
+
91
+ if (imageUrls.length > 0) {
92
+ modelInput.image_urls = imageUrls;
93
+ }
94
+
95
+ let lastStatus = "";
96
+ const result = await provider.subscribe(model, modelInput, {
97
+ timeoutMs: GENERATION_TIMEOUT_MS,
98
+ onQueueUpdate: (status) => {
99
+ if (status.status !== lastStatus) lastStatus = status.status;
100
+ },
101
+ });
102
+
103
+ const rawResult = result as Record<string, unknown>;
104
+ const data = (rawResult?.data ?? rawResult) as { images?: Array<{ url: string }> };
105
+ const imageUrl = data?.images?.[0]?.url;
106
+
107
+ onProgress?.(100);
108
+
109
+ return imageUrl ? { success: true, imageUrl } : { success: false, error: "No image generated" };
110
+ } catch (error) {
111
+ return { success: false, error: error instanceof Error ? error.message : "Generation failed" };
112
+ }
113
+ }
@@ -3,159 +3,18 @@
3
3
  * Handles image-specific generation logic
4
4
  */
5
5
 
6
- import { readFileAsBase64 } from "@umituz/react-native-design-system";
7
6
  import { createCreationsRepository } from "../../../../creations/infrastructure/adapters";
8
7
  import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
9
8
  import type { WizardStrategy } from "./wizard-strategy.types";
10
- import {
11
- GENERATION_TIMEOUT_MS,
12
- BASE64_IMAGE_PREFIX,
13
- PHOTO_KEY_PREFIX,
14
- DEFAULT_STYLE_VALUE,
15
- MODEL_INPUT_DEFAULTS,
16
- IMAGE_PROCESSING_PROMPTS,
17
- } from "./wizard-strategy.constants";
18
- import { buildFacePreservationPrompt } from "../../../../prompts/infrastructure/builders/face-preservation-builder";
19
- import { buildInteractionStylePrompt, type InteractionStyle } from "../../../../prompts/infrastructure/builders/interaction-style-builder";
9
+ import { DEFAULT_STYLE_VALUE, IMAGE_PROCESSING_PROMPTS } from "./wizard-strategy.constants";
10
+ import type { InteractionStyle } from "../../../../prompts/infrastructure/builders/interaction-style-builder";
20
11
  import { extractPrompt, extractSelection } from "../utils";
12
+ import { extractPhotosAsBase64 } from "./shared/photo-extraction.utils";
13
+ import { executeImageGeneration } from "./image-generation.executor";
14
+ import type { ImageGenerationInput, CreateImageStrategyOptions } from "./image-generation.types";
21
15
 
22
- declare const __DEV__: boolean;
23
-
24
- // ============================================================================
25
- // Types
26
- // ============================================================================
27
-
28
- export interface ImageGenerationInput {
29
- /** Photos are optional for text-to-image */
30
- readonly photos: readonly string[];
31
- readonly prompt: string;
32
- /** Optional interaction style for multi-person images */
33
- readonly interactionStyle?: InteractionStyle;
34
- /** Optional style from wizard selection */
35
- readonly style?: string;
36
- }
37
-
38
- export interface ImageGenerationResult {
39
- readonly imageUrl: string;
40
- }
41
-
42
- // ============================================================================
43
- // Photo Extraction
44
- // ============================================================================
45
-
46
- async function extractPhotosFromWizardData(
47
- wizardData: Record<string, unknown>,
48
- ): Promise<string[]> {
49
- const photoKeys = Object.keys(wizardData)
50
- .filter((k) => k.includes(PHOTO_KEY_PREFIX))
51
- .sort();
52
-
53
- if (photoKeys.length === 0) {
54
- return [];
55
- }
56
-
57
- const photoUris: string[] = [];
58
- for (const key of photoKeys) {
59
- const photo = wizardData[key] as { uri?: string };
60
- if (photo?.uri) {
61
- photoUris.push(photo.uri);
62
- }
63
- }
64
-
65
- if (photoUris.length === 0) {
66
- return [];
67
- }
68
-
69
- const photosBase64 = await Promise.all(photoUris.map((uri) => readFileAsBase64(uri)));
70
- return photosBase64.filter(Boolean) as string[];
71
- }
72
-
73
- // ============================================================================
74
- // Image Generation Executor
75
- // ============================================================================
76
-
77
- async function executeImageGeneration(
78
- input: ImageGenerationInput,
79
- model: string,
80
- onProgress?: (progress: number) => void,
81
- ): Promise<{ success: boolean; imageUrl?: string; error?: string }> {
82
- const { providerRegistry } = await import("../../../../../infrastructure/services/provider-registry.service");
83
-
84
- const provider = providerRegistry.getActiveProvider();
85
- if (!provider?.isInitialized()) {
86
- return { success: false, error: "AI provider not initialized" };
87
- }
88
-
89
- try {
90
- const formatBase64 = (base64: string): string =>
91
- base64.startsWith("data:") ? base64 : `${BASE64_IMAGE_PREFIX}${base64}`;
92
-
93
- const hasPhotos = input.photos.length > 0;
94
- const imageUrls = hasPhotos ? input.photos.map(formatBase64) : [];
95
-
96
- let finalPrompt = input.prompt;
97
-
98
- if (hasPhotos) {
99
- // Photo-based: Build face preservation prompt
100
- const facePrompt = buildFacePreservationPrompt({
101
- scenarioPrompt: input.prompt,
102
- personCount: imageUrls.length,
103
- });
104
-
105
- const interactionPrompt = buildInteractionStylePrompt({
106
- style: input.interactionStyle ?? "romantic",
107
- personCount: imageUrls.length,
108
- });
109
-
110
- finalPrompt = interactionPrompt
111
- ? `${facePrompt}\n\n${interactionPrompt}`
112
- : facePrompt;
113
-
114
- if (typeof __DEV__ !== "undefined" && __DEV__) {
115
- console.log("[ImageStrategy] Photo-based generation for", imageUrls.length, "person(s)");
116
- }
117
- } else {
118
- // Text-to-image: Use prompt with optional style
119
- if (input.style && input.style !== DEFAULT_STYLE_VALUE) {
120
- finalPrompt = `${input.prompt}. Style: ${input.style}`;
121
- }
122
-
123
- if (typeof __DEV__ !== "undefined" && __DEV__) {
124
- console.log("[ImageStrategy] Text-to-image generation");
125
- }
126
- }
127
-
128
- const modelInput: Record<string, unknown> = {
129
- prompt: finalPrompt,
130
- aspect_ratio: MODEL_INPUT_DEFAULTS.aspectRatio,
131
- output_format: MODEL_INPUT_DEFAULTS.outputFormat,
132
- num_images: MODEL_INPUT_DEFAULTS.numImages,
133
- enable_safety_checker: MODEL_INPUT_DEFAULTS.enableSafetyChecker,
134
- };
135
-
136
- if (hasPhotos) {
137
- modelInput.image_urls = imageUrls;
138
- }
139
-
140
- let lastStatus = "";
141
- const result = await provider.subscribe(model, modelInput, {
142
- timeoutMs: GENERATION_TIMEOUT_MS,
143
- onQueueUpdate: (status) => {
144
- if (status.status !== lastStatus) lastStatus = status.status;
145
- },
146
- });
147
-
148
- const rawResult = result as Record<string, unknown>;
149
- const data = (rawResult?.data ?? rawResult) as { images?: Array<{ url: string }> };
150
- const imageUrl = data?.images?.[0]?.url;
151
-
152
- onProgress?.(100);
153
-
154
- return imageUrl ? { success: true, imageUrl } : { success: false, error: "No image generated" };
155
- } catch (error) {
156
- return { success: false, error: error instanceof Error ? error.message : "Generation failed" };
157
- }
158
- }
16
+ // Re-export types for external use
17
+ export type { ImageGenerationInput, ImageGenerationResult, CreateImageStrategyOptions } from "./image-generation.types";
159
18
 
160
19
  // ============================================================================
161
20
  // Input Builder
@@ -165,69 +24,62 @@ export async function buildImageInput(
165
24
  wizardData: Record<string, unknown>,
166
25
  scenario: WizardScenarioData,
167
26
  ): Promise<ImageGenerationInput | null> {
168
- const photos = await extractPhotosFromWizardData(wizardData);
27
+ const photos = await extractPhotosAsBase64(wizardData);
169
28
 
170
- // Extract prompt using type-safe extractor with fallback
29
+ // Extract prompt with fallback to default
171
30
  let prompt = extractPrompt(wizardData, scenario.aiPrompt);
172
31
 
173
- // For image processing features, use default prompt if none provided
174
32
  if (!prompt) {
175
33
  const defaultPrompt = IMAGE_PROCESSING_PROMPTS[scenario.id];
176
34
  if (defaultPrompt) {
177
35
  prompt = defaultPrompt;
178
- if (typeof __DEV__ !== "undefined" && __DEV__) {
179
- console.log("[ImageStrategy] Using default prompt for", scenario.id);
180
- }
181
36
  } else {
182
37
  throw new Error("Prompt is required for image generation");
183
38
  }
184
39
  }
185
40
 
186
- // For photo-based generation, apply style enhancements
41
+ // Apply style enhancements for photo-based generation
187
42
  let finalPrompt = prompt;
188
43
  if (photos.length > 0) {
189
- const styleEnhancements: string[] = [];
44
+ finalPrompt = applyStyleEnhancements(prompt, wizardData);
45
+ }
190
46
 
191
- // Extract selections using type-safe extractor
192
- const romanticMoods = extractSelection(wizardData.selection_romantic_mood);
193
- if (Array.isArray(romanticMoods) && romanticMoods.length > 0) {
194
- styleEnhancements.push(`Mood: ${romanticMoods.join(", ")}`);
195
- }
47
+ // Extract style for text-to-image
48
+ const styleValue = extractSelection(wizardData.style);
49
+ const style = typeof styleValue === "string" ? styleValue : undefined;
50
+ const interactionStyle = (scenario.interactionStyle as InteractionStyle) ?? "romantic";
196
51
 
197
- const artStyle = extractSelection(wizardData.selection_art_style);
198
- if (typeof artStyle === "string" && artStyle !== DEFAULT_STYLE_VALUE) {
199
- styleEnhancements.push(`Art style: ${artStyle}`);
200
- }
52
+ return { photos, prompt: finalPrompt, style, interactionStyle };
53
+ }
201
54
 
202
- const artist = extractSelection(wizardData.selection_artist_style);
203
- if (typeof artist === "string" && artist !== DEFAULT_STYLE_VALUE) {
204
- styleEnhancements.push(`Artist style: ${artist}`);
205
- }
55
+ /**
56
+ * Applies style enhancements to the prompt
57
+ */
58
+ function applyStyleEnhancements(prompt: string, wizardData: Record<string, unknown>): string {
59
+ const enhancements: string[] = [];
206
60
 
207
- if (styleEnhancements.length > 0) {
208
- finalPrompt = `${prompt}. ${styleEnhancements.join(", ")}`;
209
- }
61
+ const romanticMoods = extractSelection(wizardData.selection_romantic_mood);
62
+ if (Array.isArray(romanticMoods) && romanticMoods.length > 0) {
63
+ enhancements.push(`Mood: ${romanticMoods.join(", ")}`);
210
64
  }
211
65
 
212
- // Extract style using type-safe extractor (for text-to-image)
213
- const styleValue = extractSelection(wizardData.style);
214
- const style = typeof styleValue === "string" ? styleValue : undefined;
66
+ const artStyle = extractSelection(wizardData.selection_art_style);
67
+ if (typeof artStyle === "string" && artStyle !== DEFAULT_STYLE_VALUE) {
68
+ enhancements.push(`Art style: ${artStyle}`);
69
+ }
215
70
 
216
- // Get interaction style from scenario (default: romantic for couple apps)
217
- const interactionStyle = (scenario.interactionStyle as InteractionStyle) ?? "romantic";
71
+ const artist = extractSelection(wizardData.selection_artist_style);
72
+ if (typeof artist === "string" && artist !== DEFAULT_STYLE_VALUE) {
73
+ enhancements.push(`Artist style: ${artist}`);
74
+ }
218
75
 
219
- return { photos, prompt: finalPrompt, style, interactionStyle };
76
+ return enhancements.length > 0 ? `${prompt}. ${enhancements.join(", ")}` : prompt;
220
77
  }
221
78
 
222
79
  // ============================================================================
223
80
  // Strategy Factory
224
81
  // ============================================================================
225
82
 
226
- export interface CreateImageStrategyOptions {
227
- readonly scenario: WizardScenarioData;
228
- readonly collectionName?: string;
229
- }
230
-
231
83
  export function createImageStrategy(options: CreateImageStrategyOptions): WizardStrategy {
232
84
  const { scenario, collectionName = "creations" } = options;
233
85
  const repository = createCreationsRepository(collectionName);
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Image Generation Types
3
+ * Type definitions for image generation strategy
4
+ */
5
+
6
+ import type { InteractionStyle } from "../../../../prompts/infrastructure/builders/interaction-style-builder";
7
+ import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
8
+
9
+ export interface ImageGenerationInput {
10
+ /** Photos are optional for text-to-image */
11
+ readonly photos: readonly string[];
12
+ readonly prompt: string;
13
+ /** Optional interaction style for multi-person images */
14
+ readonly interactionStyle?: InteractionStyle;
15
+ /** Optional style from wizard selection */
16
+ readonly style?: string;
17
+ }
18
+
19
+ export interface ImageGenerationResult {
20
+ readonly imageUrl: string;
21
+ }
22
+
23
+ export interface CreateImageStrategyOptions {
24
+ readonly scenario: WizardScenarioData;
25
+ readonly collectionName?: string;
26
+ }
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Photo Extraction Utilities
3
+ * Shared photo extraction logic for wizard strategies
4
+ */
5
+
6
+ import { readFileAsBase64 } from "@umituz/react-native-design-system";
7
+ import { PHOTO_KEY_PREFIX } from "../wizard-strategy.constants";
8
+
9
+ declare const __DEV__: boolean;
10
+
11
+ /**
12
+ * Extracts photo URIs from wizard data
13
+ */
14
+ function extractPhotoUris(wizardData: Record<string, unknown>): string[] {
15
+ const photoKeys = Object.keys(wizardData)
16
+ .filter((k) => k.includes(PHOTO_KEY_PREFIX))
17
+ .sort();
18
+
19
+ if (photoKeys.length === 0) {
20
+ return [];
21
+ }
22
+
23
+ const photoUris: string[] = [];
24
+ for (const key of photoKeys) {
25
+ const photo = wizardData[key] as { uri?: string };
26
+ if (photo?.uri) {
27
+ photoUris.push(photo.uri);
28
+ }
29
+ }
30
+
31
+ return photoUris;
32
+ }
33
+
34
+ /**
35
+ * Extracts and converts photos to base64 from wizard data
36
+ * Used by both image and video strategies
37
+ */
38
+ export async function extractPhotosAsBase64(
39
+ wizardData: Record<string, unknown>,
40
+ enableDebugLogs = false,
41
+ ): Promise<string[]> {
42
+ if (enableDebugLogs && typeof __DEV__ !== "undefined" && __DEV__) {
43
+ console.log("[PhotoExtraction] Starting extraction", {
44
+ wizardDataKeys: Object.keys(wizardData),
45
+ });
46
+ }
47
+
48
+ const photoUris = extractPhotoUris(wizardData);
49
+
50
+ if (enableDebugLogs && typeof __DEV__ !== "undefined" && __DEV__) {
51
+ console.log("[PhotoExtraction] Found photo URIs", { count: photoUris.length });
52
+ }
53
+
54
+ if (photoUris.length === 0) {
55
+ return [];
56
+ }
57
+
58
+ const photosBase64 = await Promise.all(photoUris.map((uri) => readFileAsBase64(uri)));
59
+ const validPhotos = photosBase64.filter(Boolean) as string[];
60
+
61
+ if (enableDebugLogs && typeof __DEV__ !== "undefined" && __DEV__) {
62
+ console.log("[PhotoExtraction] Converted photos", {
63
+ total: photoUris.length,
64
+ valid: validPhotos.length,
65
+ sizes: validPhotos.map((p) => `${(p.length / 1024).toFixed(1)}KB`),
66
+ });
67
+ }
68
+
69
+ return validPhotos;
70
+ }
@@ -3,122 +3,20 @@
3
3
  * Handles video-specific generation logic
4
4
  */
5
5
 
6
- import { readFileAsBase64 } from "@umituz/react-native-design-system";
7
- import type { VideoFeatureType } from "../../../../../domain/interfaces";
8
6
  import { executeVideoFeature } from "../../../../../infrastructure/services/video-feature-executor.service";
9
7
  import { createCreationsRepository } from "../../../../creations/infrastructure/adapters";
10
8
  import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
11
9
  import type { WizardStrategy } from "./wizard-strategy.types";
12
- import { PHOTO_KEY_PREFIX, VIDEO_FEATURE_PATTERNS, VIDEO_PROCESSING_PROMPTS } from "./wizard-strategy.constants";
13
-
14
- declare const __DEV__: boolean;
10
+ import { VIDEO_PROCESSING_PROMPTS } from "./wizard-strategy.constants";
15
11
  import { extractPrompt, extractDuration, extractAspectRatio, extractResolution } from "../utils";
12
+ import { extractPhotosAsBase64 } from "./shared/photo-extraction.utils";
13
+ import { getVideoFeatureType } from "./video-generation.utils";
14
+ import type { VideoGenerationInput, CreateVideoStrategyOptions } from "./video-generation.types";
16
15
 
17
- // ============================================================================
18
- // Types
19
- // ============================================================================
20
-
21
- export interface VideoGenerationInput {
22
- /** Source image (optional for text-to-video) */
23
- readonly sourceImageBase64?: string;
24
- /** Target image (optional, uses source if not provided) */
25
- readonly targetImageBase64?: string;
26
- readonly prompt: string;
27
- /** Video duration in seconds */
28
- readonly duration?: number;
29
- /** Aspect ratio (e.g., "16:9", "9:16") */
30
- readonly aspectRatio?: string;
31
- /** Video resolution (e.g., "720p", "1080p") */
32
- readonly resolution?: string;
33
- }
34
-
35
- export interface VideoGenerationResult {
36
- readonly videoUrl: string;
37
- }
38
-
39
- // ============================================================================
40
- // Photo Extraction
41
- // ============================================================================
42
-
43
- async function extractPhotosFromWizardData(
44
- wizardData: Record<string, unknown>,
45
- ): Promise<string[]> {
46
- if (typeof __DEV__ !== "undefined" && __DEV__) {
47
- console.log("[VideoStrategy:extractPhotos] Starting extraction", {
48
- wizardDataKeys: Object.keys(wizardData),
49
- });
50
- }
51
-
52
- const photoKeys = Object.keys(wizardData)
53
- .filter((k) => k.includes(PHOTO_KEY_PREFIX))
54
- .sort();
55
-
56
- if (typeof __DEV__ !== "undefined" && __DEV__) {
57
- console.log("[VideoStrategy:extractPhotos] Found photo keys", { photoKeys });
58
- }
59
-
60
- if (photoKeys.length === 0) {
61
- if (typeof __DEV__ !== "undefined" && __DEV__) {
62
- console.log("[VideoStrategy:extractPhotos] No photo keys found");
63
- }
64
- return [];
65
- }
66
-
67
- const photoUris: string[] = [];
68
- for (const key of photoKeys) {
69
- const photo = wizardData[key] as { uri?: string };
70
- if (photo?.uri) {
71
- photoUris.push(photo.uri);
72
- if (typeof __DEV__ !== "undefined" && __DEV__) {
73
- console.log("[VideoStrategy:extractPhotos] Found photo URI", { key, uri: photo.uri.substring(0, 50) + "..." });
74
- }
75
- }
76
- }
77
-
78
- if (photoUris.length === 0) {
79
- if (typeof __DEV__ !== "undefined" && __DEV__) {
80
- console.log("[VideoStrategy:extractPhotos] No photo URIs found");
81
- }
82
- return [];
83
- }
84
-
85
- if (typeof __DEV__ !== "undefined" && __DEV__) {
86
- console.log("[VideoStrategy:extractPhotos] Converting to base64", { count: photoUris.length });
87
- }
88
-
89
- const photosBase64 = await Promise.all(photoUris.map((uri) => readFileAsBase64(uri)));
90
- const validPhotos = photosBase64.filter(Boolean) as string[];
91
-
92
- if (typeof __DEV__ !== "undefined" && __DEV__) {
93
- console.log("[VideoStrategy:extractPhotos] Converted photos", {
94
- total: photoUris.length,
95
- valid: validPhotos.length,
96
- sizes: validPhotos.map((p) => `${(p.length / 1024).toFixed(1)}KB`),
97
- });
98
- }
99
-
100
- return validPhotos;
101
- }
102
-
103
- // ============================================================================
104
- // Video Feature Type Detection
105
- // ============================================================================
106
-
107
- function getVideoFeatureType(scenarioId: string): VideoFeatureType {
108
- const id = scenarioId.toLowerCase();
109
-
110
- for (const [pattern, featureType] of Object.entries(VIDEO_FEATURE_PATTERNS)) {
111
- if (id.includes(pattern)) {
112
- return featureType;
113
- }
114
- }
16
+ declare const __DEV__: boolean;
115
17
 
116
- // Default to image-to-video for content scenarios (they take a photo and generate video)
117
- if (typeof __DEV__ !== "undefined" && __DEV__) {
118
- console.log("[VideoStrategy:getFeatureType] Defaulting to image-to-video for content scenario", { scenarioId });
119
- }
120
- return "image-to-video";
121
- }
18
+ // Re-export types for external use
19
+ export type { VideoGenerationInput, VideoGenerationResult, CreateVideoStrategyOptions } from "./video-generation.types";
122
20
 
123
21
  // ============================================================================
124
22
  // Input Builder
@@ -129,79 +27,37 @@ export async function buildVideoInput(
129
27
  scenario: WizardScenarioData,
130
28
  ): Promise<VideoGenerationInput | null> {
131
29
  if (typeof __DEV__ !== "undefined" && __DEV__) {
132
- console.log("[VideoStrategy:buildInput] START", {
133
- scenarioId: scenario.id,
134
- outputType: scenario.outputType,
135
- hasAiPrompt: !!scenario.aiPrompt,
136
- });
30
+ console.log("[VideoStrategy] Building input", { scenarioId: scenario.id });
137
31
  }
138
32
 
139
- const photos = await extractPhotosFromWizardData(wizardData);
140
-
141
- if (typeof __DEV__ !== "undefined" && __DEV__) {
142
- console.log("[VideoStrategy:buildInput] Photos extracted", {
143
- count: photos.length,
144
- hasSource: !!photos[0],
145
- hasTarget: !!photos[1],
146
- });
147
- }
33
+ const photos = await extractPhotosAsBase64(wizardData, true);
148
34
 
149
- // Extract prompt using type-safe extractor with fallback
35
+ // Extract prompt with fallback to default
150
36
  let prompt = extractPrompt(wizardData, scenario.aiPrompt);
151
37
 
152
- if (typeof __DEV__ !== "undefined" && __DEV__) {
153
- console.log("[VideoStrategy:buildInput] Prompt from wizard", {
154
- hasPrompt: !!prompt,
155
- promptLength: prompt?.length ?? 0,
156
- });
157
- }
158
-
159
- // For video processing features, use default prompt if none provided
160
38
  if (!prompt) {
161
39
  const defaultPrompt = VIDEO_PROCESSING_PROMPTS[scenario.id];
162
40
  if (defaultPrompt) {
163
41
  prompt = defaultPrompt;
164
- if (typeof __DEV__ !== "undefined" && __DEV__) {
165
- console.log("[VideoStrategy:buildInput] Using default prompt", {
166
- scenarioId: scenario.id,
167
- prompt: prompt.substring(0, 50) + "...",
168
- });
169
- }
170
42
  } else {
171
- if (typeof __DEV__ !== "undefined" && __DEV__) {
172
- console.log("[VideoStrategy:buildInput] ERROR: No prompt available", {
173
- scenarioId: scenario.id,
174
- availablePrompts: Object.keys(VIDEO_PROCESSING_PROMPTS),
175
- });
176
- }
177
43
  throw new Error("Prompt is required for video generation");
178
44
  }
179
45
  }
180
46
 
181
- // Extract video generation parameters
182
- const duration = extractDuration(wizardData);
183
- const aspectRatio = extractAspectRatio(wizardData);
184
- const resolution = extractResolution(wizardData);
185
-
186
47
  const input: VideoGenerationInput = {
187
48
  sourceImageBase64: photos[0],
188
49
  targetImageBase64: photos[1] || photos[0],
189
50
  prompt,
190
- duration,
191
- aspectRatio,
192
- resolution,
51
+ duration: extractDuration(wizardData),
52
+ aspectRatio: extractAspectRatio(wizardData),
53
+ resolution: extractResolution(wizardData),
193
54
  };
194
55
 
195
56
  if (typeof __DEV__ !== "undefined" && __DEV__) {
196
- console.log("[VideoStrategy:buildInput] COMPLETE", {
57
+ console.log("[VideoStrategy] Input built", {
197
58
  hasSource: !!input.sourceImageBase64,
198
59
  hasTarget: !!input.targetImageBase64,
199
- sourceSize: input.sourceImageBase64 ? `${(input.sourceImageBase64.length / 1024).toFixed(1)}KB` : "N/A",
200
- targetSize: input.targetImageBase64 ? `${(input.targetImageBase64.length / 1024).toFixed(1)}KB` : "N/A",
201
- prompt: input.prompt.substring(0, 50) + "...",
202
60
  duration: input.duration,
203
- aspectRatio: input.aspectRatio,
204
- resolution: input.resolution,
205
61
  });
206
62
  }
207
63
 
@@ -212,22 +68,13 @@ export async function buildVideoInput(
212
68
  // Strategy Factory
213
69
  // ============================================================================
214
70
 
215
- export interface CreateVideoStrategyOptions {
216
- readonly scenario: WizardScenarioData;
217
- readonly collectionName?: string;
218
- }
219
-
220
71
  export function createVideoStrategy(options: CreateVideoStrategyOptions): WizardStrategy {
221
72
  const { scenario, collectionName = "creations" } = options;
222
73
  const repository = createCreationsRepository(collectionName);
223
74
  const videoFeatureType = getVideoFeatureType(scenario.id);
224
75
 
225
76
  if (typeof __DEV__ !== "undefined" && __DEV__) {
226
- console.log("[VideoStrategy:create] Created strategy", {
227
- scenarioId: scenario.id,
228
- videoFeatureType,
229
- collectionName,
230
- });
77
+ console.log("[VideoStrategy] Created", { scenarioId: scenario.id, videoFeatureType });
231
78
  }
232
79
 
233
80
  let lastInputRef: VideoGenerationInput | null = null;
@@ -237,51 +84,21 @@ export function createVideoStrategy(options: CreateVideoStrategyOptions): Wizard
237
84
  const videoInput = input as VideoGenerationInput;
238
85
  lastInputRef = videoInput;
239
86
 
240
- if (typeof __DEV__ !== "undefined" && __DEV__) {
241
- console.log("[VideoStrategy:execute] START", {
242
- featureType: videoFeatureType,
243
- hasSource: !!videoInput.sourceImageBase64,
244
- hasTarget: !!videoInput.targetImageBase64,
245
- prompt: videoInput.prompt.substring(0, 50) + "...",
246
- });
247
- }
248
-
249
- const result = await executeVideoFeature(
250
- videoFeatureType,
251
- {
252
- sourceImageBase64: videoInput.sourceImageBase64,
253
- targetImageBase64: videoInput.targetImageBase64,
254
- prompt: videoInput.prompt,
255
- options: {
256
- duration: videoInput.duration,
257
- aspect_ratio: videoInput.aspectRatio,
258
- resolution: videoInput.resolution,
259
- },
87
+ const result = await executeVideoFeature(videoFeatureType, {
88
+ sourceImageBase64: videoInput.sourceImageBase64,
89
+ targetImageBase64: videoInput.targetImageBase64,
90
+ prompt: videoInput.prompt,
91
+ options: {
92
+ duration: videoInput.duration,
93
+ aspect_ratio: videoInput.aspectRatio,
94
+ resolution: videoInput.resolution,
260
95
  },
261
- );
262
-
263
- if (typeof __DEV__ !== "undefined" && __DEV__) {
264
- console.log("[VideoStrategy:execute] RESULT", {
265
- success: result.success,
266
- hasVideoUrl: !!result.videoUrl,
267
- error: result.error,
268
- requestId: result.requestId,
269
- });
270
- }
96
+ });
271
97
 
272
98
  if (!result.success || !result.videoUrl) {
273
- if (typeof __DEV__ !== "undefined" && __DEV__) {
274
- console.log("[VideoStrategy:execute] FAILED", { error: result.error });
275
- }
276
99
  throw new Error(result.error || "Video generation failed");
277
100
  }
278
101
 
279
- if (typeof __DEV__ !== "undefined" && __DEV__) {
280
- console.log("[VideoStrategy:execute] SUCCESS", {
281
- videoUrl: result.videoUrl.substring(0, 80) + "...",
282
- });
283
- }
284
-
285
102
  return { videoUrl: result.videoUrl };
286
103
  },
287
104
 
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Video Generation Types
3
+ * Type definitions for video generation strategy
4
+ */
5
+
6
+ import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
7
+
8
+ export interface VideoGenerationInput {
9
+ /** Source image (optional for text-to-video) */
10
+ readonly sourceImageBase64?: string;
11
+ /** Target image (optional, uses source if not provided) */
12
+ readonly targetImageBase64?: string;
13
+ readonly prompt: string;
14
+ /** Video duration in seconds */
15
+ readonly duration?: number;
16
+ /** Aspect ratio (e.g., "16:9", "9:16") */
17
+ readonly aspectRatio?: string;
18
+ /** Video resolution (e.g., "720p", "1080p") */
19
+ readonly resolution?: string;
20
+ }
21
+
22
+ export interface VideoGenerationResult {
23
+ readonly videoUrl: string;
24
+ }
25
+
26
+ export interface CreateVideoStrategyOptions {
27
+ readonly scenario: WizardScenarioData;
28
+ readonly collectionName?: string;
29
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Video Generation Utilities
3
+ * Video-specific utility functions
4
+ */
5
+
6
+ import type { VideoFeatureType } from "../../../../../domain/interfaces";
7
+ import { VIDEO_FEATURE_PATTERNS } from "./wizard-strategy.constants";
8
+
9
+ declare const __DEV__: boolean;
10
+
11
+ /**
12
+ * Determines the video feature type based on scenario ID
13
+ */
14
+ export function getVideoFeatureType(scenarioId: string): VideoFeatureType {
15
+ const id = scenarioId.toLowerCase();
16
+
17
+ for (const [pattern, featureType] of Object.entries(VIDEO_FEATURE_PATTERNS)) {
18
+ if (id.includes(pattern)) {
19
+ return featureType;
20
+ }
21
+ }
22
+
23
+ // Default to image-to-video for content scenarios
24
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
25
+ console.log("[VideoUtils] Defaulting to image-to-video", { scenarioId });
26
+ }
27
+ return "image-to-video";
28
+ }
@@ -43,7 +43,7 @@ export interface GenericWizardFlowProps {
43
43
  readonly scenario?: WizardScenarioData;
44
44
  readonly scenarioId?: string;
45
45
  readonly userId?: string;
46
- readonly alertMessages?: AlertMessages;
46
+ readonly alertMessages: AlertMessages;
47
47
  readonly skipResultStep?: boolean;
48
48
  readonly onStepChange?: (stepId: string, stepType: StepType | string) => void;
49
49
  readonly onGenerationStart?: (
@@ -89,14 +89,18 @@ export {
89
89
  PHOTOREALISTIC_RENDERING,
90
90
  NATURAL_POSE_GUIDELINES,
91
91
  MASTER_BASE_PROMPT,
92
- MULTI_PERSON_PRESERVATION_RULES,
93
92
  createEnhancedPrompt,
94
93
  createTransformationPrompt,
95
94
  enhanceExistingPrompt,
96
- createMultiPersonPrompt,
97
95
  } from './domain/entities/BasePromptStructure';
98
96
  export type { CreatePromptOptions } from './domain/entities/BasePromptStructure';
99
97
 
98
+ export {
99
+ MULTI_PERSON_PRESERVATION_RULES,
100
+ createMultiPersonPrompt,
101
+ } from './domain/entities/MultiPersonPromptStructure';
102
+ export type { MultiPersonPreservationRules } from './domain/entities/MultiPersonPromptStructure';
103
+
100
104
  export {
101
105
  buildFacePreservationPrompt,
102
106
  buildMinimalFacePreservationPrompt,