@umituz/react-native-ai-generation-content 1.75.2 → 1.75.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.75.2",
3
+ "version": "1.75.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",
@@ -61,25 +61,41 @@ export async function executeVideoGeneration(
61
61
  });
62
62
  }
63
63
 
64
+ // Detect Minimax subject reference model
65
+ const isSubjectReference = model.includes("minimax") || model.includes("hailuo");
66
+
64
67
  const modelInput: Record<string, unknown> = {
65
68
  prompt: input.prompt,
66
69
  };
67
70
 
68
- // Add image for image-to-video (Sora 2, etc.)
69
- // Only add if sourceImage is defined and not empty
70
- if (sourceImage && sourceImage.length > 0) {
71
- modelInput.image_url = sourceImage;
72
- }
73
-
74
- // Add optional parameters
75
- if (input.duration) {
76
- modelInput.duration = input.duration;
77
- }
78
- if (input.aspectRatio) {
79
- modelInput.aspect_ratio = input.aspectRatio;
80
- }
81
- if (input.resolution) {
82
- modelInput.resolution = input.resolution;
71
+ if (isSubjectReference) {
72
+ // Minimax Hailuo: Use subject_reference_image_url for face/identity only
73
+ if (sourceImage && sourceImage.length > 0) {
74
+ modelInput.subject_reference_image_url = sourceImage;
75
+ }
76
+ modelInput.prompt_optimizer = true;
77
+ // Minimax expects duration as string
78
+ if (input.duration) {
79
+ modelInput.duration = String(input.duration);
80
+ }
81
+ // Minimax uses uppercase resolution format (512P, 768P)
82
+ if (input.resolution) {
83
+ modelInput.resolution = input.resolution;
84
+ }
85
+ } else {
86
+ // Standard models (Grok, etc.): Use image_url
87
+ if (sourceImage && sourceImage.length > 0) {
88
+ modelInput.image_url = sourceImage;
89
+ }
90
+ if (input.duration) {
91
+ modelInput.duration = input.duration;
92
+ }
93
+ if (input.aspectRatio) {
94
+ modelInput.aspect_ratio = input.aspectRatio;
95
+ }
96
+ if (input.resolution) {
97
+ modelInput.resolution = input.resolution;
98
+ }
83
99
  }
84
100
 
85
101
  let lastStatus = "";
@@ -131,25 +147,36 @@ export async function submitVideoGenerationToQueue(
131
147
  try {
132
148
  const sourceImage = formatBase64(input.sourceImageBase64);
133
149
 
150
+ const isSubjectReference = model.includes("minimax") || model.includes("hailuo");
151
+
134
152
  const modelInput: Record<string, unknown> = {
135
153
  prompt: input.prompt,
136
154
  };
137
155
 
138
- // Add image for image-to-video
139
- // Only add if sourceImage is defined and not empty
140
- if (sourceImage && sourceImage.length > 0) {
141
- modelInput.image_url = sourceImage;
142
- }
143
-
144
- // Add optional parameters
145
- if (input.duration) {
146
- modelInput.duration = input.duration;
147
- }
148
- if (input.aspectRatio) {
149
- modelInput.aspect_ratio = input.aspectRatio;
150
- }
151
- if (input.resolution) {
152
- modelInput.resolution = input.resolution;
156
+ if (isSubjectReference) {
157
+ if (sourceImage && sourceImage.length > 0) {
158
+ modelInput.subject_reference_image_url = sourceImage;
159
+ }
160
+ modelInput.prompt_optimizer = true;
161
+ if (input.duration) {
162
+ modelInput.duration = String(input.duration);
163
+ }
164
+ if (input.resolution) {
165
+ modelInput.resolution = input.resolution;
166
+ }
167
+ } else {
168
+ if (sourceImage && sourceImage.length > 0) {
169
+ modelInput.image_url = sourceImage;
170
+ }
171
+ if (input.duration) {
172
+ modelInput.duration = input.duration;
173
+ }
174
+ if (input.aspectRatio) {
175
+ modelInput.aspect_ratio = input.aspectRatio;
176
+ }
177
+ if (input.resolution) {
178
+ modelInput.resolution = input.resolution;
179
+ }
153
180
  }
154
181
 
155
182
  const submission = await provider.submitJob(model, modelInput);
@@ -2,24 +2,42 @@
2
2
  * Credit Value Extractors
3
3
  * Pure utility functions to extract and normalize values from customData
4
4
  * Single Responsibility: Data transformation for credit calculation
5
+ *
6
+ * Handles both raw values and selection format objects:
7
+ * - Raw: "4s", 4, "480p"
8
+ * - Selection format: { uri: "4s", selection: "4s" | 4, previewUrl: "" }
9
+ */
10
+
11
+ /**
12
+ * Unwrap selection format to get the actual value
13
+ * Selection steps store data as { uri, selection, previewUrl } objects
5
14
  */
15
+ function unwrapSelection(value: unknown): unknown {
16
+ if (typeof value === "object" && value !== null && "selection" in value) {
17
+ return (value as Record<string, unknown>).selection;
18
+ }
19
+ return value;
20
+ }
6
21
 
7
22
  /**
8
23
  * Extract duration value from customData
9
24
  * Handles both number and string formats ("4s", "5s", "6s")
25
+ * Also handles selection format objects from wizard steps
10
26
  *
11
27
  * @param value - Raw value from customData
12
28
  * @returns Normalized duration number, or undefined if invalid
13
29
  */
14
30
  export function extractDuration(value: unknown): number | undefined {
31
+ const unwrapped = unwrapSelection(value);
32
+
15
33
  // Already a number
16
- if (typeof value === "number" && value > 0) {
17
- return value;
34
+ if (typeof unwrapped === "number" && unwrapped > 0) {
35
+ return unwrapped;
18
36
  }
19
37
 
20
38
  // String format: "4s", "5s", "6s" → parse to number
21
- if (typeof value === "string") {
22
- const match = value.match(/^(\d+)s?$/);
39
+ if (typeof unwrapped === "string") {
40
+ const match = unwrapped.match(/^(\d+)s?$/);
23
41
  if (match) {
24
42
  const parsed = parseInt(match[1], 10);
25
43
  return parsed > 0 ? parsed : undefined;
@@ -32,13 +50,16 @@ export function extractDuration(value: unknown): number | undefined {
32
50
  /**
33
51
  * Extract resolution value from customData
34
52
  * Validates against allowed values
53
+ * Also handles selection format objects from wizard steps
35
54
  *
36
55
  * @param value - Raw value from customData
37
56
  * @returns Normalized resolution string, or undefined if invalid
38
57
  */
39
- export function extractResolution(value: unknown): "480p" | "720p" | undefined {
40
- if (value === "480p" || value === "720p") {
41
- return value;
58
+ export function extractResolution(value: unknown): string | undefined {
59
+ const unwrapped = unwrapSelection(value);
60
+
61
+ if (typeof unwrapped === "string" && unwrapped.length > 0) {
62
+ return unwrapped;
42
63
  }
43
64
  return undefined;
44
65
  }
@@ -2,8 +2,14 @@
2
2
  * Wizard Data Validators
3
3
  * Centralized validation utilities for wizard generation data
4
4
  * DRY: Used across AIGenerateScreen, TextToVideoWizardScreen, ImageToVideoWizardScreen
5
+ *
6
+ * Handles both raw values and selection format objects from wizard steps:
7
+ * - Raw: 4, "480p"
8
+ * - Selection format: { uri: "4s", selection: 4, previewUrl: "" }
5
9
  */
6
10
 
11
+ import { extractDuration, extractResolution } from "./credit-value-extractors";
12
+
7
13
  export interface ValidationResult<T> {
8
14
  value?: T;
9
15
  error?: string;
@@ -18,11 +24,11 @@ export interface ValidationResult<T> {
18
24
  export function validateDuration(
19
25
  data: Record<string, unknown>,
20
26
  ): ValidationResult<number> {
21
- const duration = data.duration as number;
27
+ const duration = extractDuration(data.duration);
22
28
 
23
- if (!duration || typeof duration !== "number" || duration <= 0) {
29
+ if (!duration) {
24
30
  return {
25
- error: `Invalid duration: ${duration}. Must be a positive number.`,
31
+ error: `Invalid duration: ${JSON.stringify(data.duration)}. Must be a positive number.`,
26
32
  };
27
33
  }
28
34
 
@@ -37,25 +43,14 @@ export function validateDuration(
37
43
  */
38
44
  export function validateResolution(
39
45
  data: Record<string, unknown>,
40
- ): ValidationResult<"480p" | "720p"> {
41
- const resolutionValue = data.resolution as string;
46
+ ): ValidationResult<string> {
47
+ const resolution = extractResolution(data.resolution);
42
48
 
43
- if (!resolutionValue || typeof resolutionValue !== "string") {
49
+ if (!resolution) {
44
50
  return {
45
- error: `Invalid resolution: ${resolutionValue}. Must be a string.`,
51
+ error: `Invalid resolution: ${JSON.stringify(data.resolution)}. Must be a valid resolution string.`,
46
52
  };
47
53
  }
48
54
 
49
- // Map resolution - EXACT MATCH ONLY
50
- if (resolutionValue === "480p") {
51
- return { value: "480p" };
52
- }
53
-
54
- if (resolutionValue === "720p") {
55
- return { value: "720p" };
56
- }
57
-
58
- return {
59
- error: `Invalid resolution value: "${resolutionValue}". Must be "480p" or "720p".`,
60
- };
55
+ return { value: resolution };
61
56
  }
@@ -94,8 +94,13 @@ export function useWizardFlowHandlers(props: UseWizardFlowHandlersProps) {
94
94
  else previousStep();
95
95
  }, [currentStepIndex, previousStep, onBack]);
96
96
 
97
- const handleNextStep = useCallback(() => {
97
+ const handleNextStep = useCallback((additionalData?: Record<string, unknown>) => {
98
98
  const nextStepDef = flowSteps[currentStepIndex + 1];
99
+ // Merge additionalData to avoid stale closure issue
100
+ // When called from handlePhotoContinue, customData in closure may not include the just-set value
101
+ // Guard: Only merge plain objects (ignore SyntheticEvents from onPress handlers)
102
+ const isPlainObject = additionalData && typeof additionalData === "object" && !("nativeEvent" in additionalData) && !Array.isArray(additionalData);
103
+ const mergedData = isPlainObject ? { ...customData, ...additionalData } : customData;
99
104
  if (typeof __DEV__ !== "undefined" && __DEV__) {
100
105
  console.log("[handleNextStep] Called", {
101
106
  currentStepIndex,
@@ -103,14 +108,15 @@ export function useWizardFlowHandlers(props: UseWizardFlowHandlersProps) {
103
108
  nextStepId: nextStepDef?.id,
104
109
  totalSteps: flowSteps.length,
105
110
  hasOnGenerationStart: !!onGenerationStart,
111
+ dataKeys: Object.keys(mergedData),
106
112
  });
107
113
  }
108
114
  if (nextStepDef?.type === StepType.GENERATING && onGenerationStart) {
109
- onGenerationStart(customData, nextStep);
115
+ onGenerationStart(mergedData, nextStep, handleGenerationError);
110
116
  return;
111
117
  }
112
118
  nextStep();
113
- }, [currentStepIndex, flowSteps, customData, onGenerationStart, nextStep]);
119
+ }, [currentStepIndex, flowSteps, customData, onGenerationStart, nextStep, handleGenerationError]);
114
120
 
115
121
  const handlePhotoContinue = useCallback(
116
122
  (stepId: string, image: UploadedImage) => {
@@ -118,7 +124,8 @@ export function useWizardFlowHandlers(props: UseWizardFlowHandlersProps) {
118
124
  console.log("[handlePhotoContinue] Called", { stepId, hasImage: !!image, currentStepIndex });
119
125
  }
120
126
  setCustomData(stepId, image);
121
- handleNextStep();
127
+ // Pass the just-set data to avoid stale closure issue
128
+ handleNextStep({ [stepId]: image });
122
129
  },
123
130
  [setCustomData, handleNextStep, currentStepIndex],
124
131
  );