@umituz/react-native-ai-generation-content 1.77.0 → 1.79.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 +1 -1
- package/src/domain/interfaces/index.ts +3 -0
- package/src/domain/interfaces/video-model-config.types.ts +52 -0
- package/src/domains/generation/wizard/infrastructure/strategies/shared/photo-extraction.utils.ts +49 -1
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.executor.ts +39 -67
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts +3 -5
- package/src/domains/generation/wizard/infrastructure/strategies/video-generation.types.ts +3 -0
- package/src/domains/generation/wizard/infrastructure/strategies/wizard-strategy.factory.ts +5 -2
- package/src/domains/generation/wizard/presentation/components/GenericWizardFlow.tsx +5 -0
- package/src/domains/generation/wizard/presentation/components/WizardFlowContent.tsx +5 -0
- package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +2 -1
- package/src/domains/generation/wizard/presentation/hooks/videoQueuePoller.ts +34 -1
- package/src/domains/generation/wizard/presentation/hooks/wizard-generation.types.ts +3 -0
- package/src/domains/generation/wizard/utilities/build-wizard-config.ts +76 -0
- package/src/index.ts +6 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.79.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",
|
|
@@ -13,3 +13,6 @@ export type { IAIProviderJobManager } from "../../domains/background/domain/inte
|
|
|
13
13
|
export type { IAIProviderExecutor } from "./provider-executor.interface";
|
|
14
14
|
export type { IAIProviderImageFeatures } from "./provider-image-features.interface";
|
|
15
15
|
export type { IAIProviderVideoFeatures } from "./provider-video-features.interface";
|
|
16
|
+
|
|
17
|
+
// Video Model Configuration
|
|
18
|
+
export type { VideoModelConfig, ModelCapabilityOption } from "./video-model-config.types";
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VideoModelConfig - Model-Agnostic Configuration Interface
|
|
3
|
+
* Encapsulates ALL model-specific behavior in a single config object.
|
|
4
|
+
* Adding a new model = creating one config file. No other changes needed.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export interface ModelCapabilityOption {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly label: string;
|
|
10
|
+
readonly value: string | number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface VideoModelConfig {
|
|
14
|
+
/** Fal.ai model endpoint (e.g., "fal-ai/minimax/hailuo-02/standard/image-to-video") */
|
|
15
|
+
readonly modelId: string;
|
|
16
|
+
|
|
17
|
+
/** Human-readable display name */
|
|
18
|
+
readonly displayName: string;
|
|
19
|
+
|
|
20
|
+
/** What this model supports (drives wizard UI) */
|
|
21
|
+
readonly capabilities: {
|
|
22
|
+
readonly resolutions: ReadonlyArray<ModelCapabilityOption>;
|
|
23
|
+
readonly durations: ReadonlyArray<ModelCapabilityOption>;
|
|
24
|
+
readonly aspectRatios?: ReadonlyArray<ModelCapabilityOption>;
|
|
25
|
+
readonly defaults: {
|
|
26
|
+
readonly resolution: string;
|
|
27
|
+
readonly duration: number;
|
|
28
|
+
readonly aspectRatio?: string;
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Maps generic WizardVideoInput to model-specific API parameters.
|
|
34
|
+
* This is the core adapter function - eliminates all model-specific if/else checks.
|
|
35
|
+
*/
|
|
36
|
+
readonly buildInput: (input: {
|
|
37
|
+
readonly prompt: string;
|
|
38
|
+
readonly sourceImageBase64?: string;
|
|
39
|
+
readonly targetImageBase64?: string;
|
|
40
|
+
readonly duration?: number;
|
|
41
|
+
readonly aspectRatio?: string;
|
|
42
|
+
readonly resolution?: string;
|
|
43
|
+
}) => Record<string, unknown>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Pricing data for credit calculation.
|
|
47
|
+
* Keys are resolution IDs matching capabilities.resolutions[].id
|
|
48
|
+
*/
|
|
49
|
+
readonly pricing: {
|
|
50
|
+
readonly costPerSecond: Record<string, number>;
|
|
51
|
+
};
|
|
52
|
+
}
|
package/src/domains/generation/wizard/infrastructure/strategies/shared/photo-extraction.utils.ts
CHANGED
|
@@ -4,10 +4,57 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { readFileAsBase64 } from "@umituz/react-native-design-system";
|
|
7
|
+
import { manipulateAsync, SaveFormat } from "expo-image-manipulator";
|
|
8
|
+
import { Image } from "react-native";
|
|
7
9
|
import { PHOTO_KEY_PREFIX } from "../wizard-strategy.constants";
|
|
8
10
|
|
|
9
11
|
declare const __DEV__: boolean;
|
|
10
12
|
|
|
13
|
+
const MIN_IMAGE_DIMENSION = 300;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get image dimensions from URI
|
|
17
|
+
*/
|
|
18
|
+
function getImageSize(uri: string): Promise<{ width: number; height: number }> {
|
|
19
|
+
return new Promise((resolve, reject) => {
|
|
20
|
+
Image.getSize(uri, (width, height) => resolve({ width, height }), reject);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Ensure image meets minimum dimensions (300x300) required by AI providers.
|
|
26
|
+
* Returns the original URI if already large enough, or a resized URI.
|
|
27
|
+
*/
|
|
28
|
+
async function ensureMinimumSize(uri: string): Promise<string> {
|
|
29
|
+
try {
|
|
30
|
+
const { width, height } = await getImageSize(uri);
|
|
31
|
+
|
|
32
|
+
if (width >= MIN_IMAGE_DIMENSION && height >= MIN_IMAGE_DIMENSION) {
|
|
33
|
+
return uri;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const scale = Math.max(MIN_IMAGE_DIMENSION / width, MIN_IMAGE_DIMENSION / height);
|
|
37
|
+
const newWidth = Math.ceil(width * scale);
|
|
38
|
+
const newHeight = Math.ceil(height * scale);
|
|
39
|
+
|
|
40
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
41
|
+
console.log("[PhotoExtraction] Resizing small image", {
|
|
42
|
+
from: `${width}x${height}`,
|
|
43
|
+
to: `${newWidth}x${newHeight}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const result = await manipulateAsync(uri, [{ resize: { width: newWidth, height: newHeight } }], {
|
|
48
|
+
format: SaveFormat.JPEG,
|
|
49
|
+
compress: 0.9,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return result.uri;
|
|
53
|
+
} catch {
|
|
54
|
+
return uri;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
11
58
|
/**
|
|
12
59
|
* Extracts photo URIs from wizard data
|
|
13
60
|
*/
|
|
@@ -59,7 +106,8 @@ export async function extractPhotosAsBase64(
|
|
|
59
106
|
const results = await Promise.allSettled(
|
|
60
107
|
photoUris.map(async (uri, index) => {
|
|
61
108
|
try {
|
|
62
|
-
|
|
109
|
+
const resizedUri = await ensureMinimumSize(uri);
|
|
110
|
+
return await readFileAsBase64(resizedUri);
|
|
63
111
|
} catch (error) {
|
|
64
112
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
65
113
|
console.error(`[PhotoExtraction] Failed to read photo ${index}:`, error);
|
package/src/domains/generation/wizard/infrastructure/strategies/video-generation.executor.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Video Generation Executor
|
|
3
|
-
* Handles the actual video generation execution
|
|
4
|
-
*
|
|
3
|
+
* Handles the actual video generation execution.
|
|
4
|
+
* Model-agnostic: uses VideoModelConfig.buildInput() for model-specific parameters.
|
|
5
|
+
* Fallback: generic input builder when no modelConfig is provided.
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
import type { WizardVideoInput } from "./video-generation.types";
|
|
9
|
+
import type { VideoModelConfig } from "../../../../../domain/interfaces/video-model-config.types";
|
|
8
10
|
import { GENERATION_TIMEOUT_MS, BASE64_IMAGE_PREFIX } from "./wizard-strategy.constants";
|
|
9
11
|
import { createGenerationError, GenerationErrorType } from "../../../../../infrastructure/utils/error-factory";
|
|
10
12
|
|
|
@@ -29,14 +31,38 @@ function formatBase64(base64: string | undefined): string | undefined {
|
|
|
29
31
|
return base64.startsWith("data:") ? base64 : `${BASE64_IMAGE_PREFIX}${base64}`;
|
|
30
32
|
}
|
|
31
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Generic input builder - used when no modelConfig is provided.
|
|
36
|
+
* Sends standard parameters without model-specific logic.
|
|
37
|
+
*/
|
|
38
|
+
function buildGenericInput(input: WizardVideoInput): Record<string, unknown> {
|
|
39
|
+
const modelInput: Record<string, unknown> = { prompt: input.prompt };
|
|
40
|
+
const sourceImage = formatBase64(input.sourceImageBase64);
|
|
41
|
+
|
|
42
|
+
if (sourceImage && sourceImage.length > 0) {
|
|
43
|
+
modelInput.image_url = sourceImage;
|
|
44
|
+
}
|
|
45
|
+
if (input.duration) {
|
|
46
|
+
modelInput.duration = input.duration;
|
|
47
|
+
}
|
|
48
|
+
if (input.aspectRatio) {
|
|
49
|
+
modelInput.aspect_ratio = input.aspectRatio;
|
|
50
|
+
}
|
|
51
|
+
if (input.resolution) {
|
|
52
|
+
modelInput.resolution = input.resolution;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return modelInput;
|
|
56
|
+
}
|
|
57
|
+
|
|
32
58
|
/**
|
|
33
59
|
* Execute video generation using direct provider call
|
|
34
|
-
* For text-to-video and image-to-video generation models (NOT features)
|
|
35
60
|
*/
|
|
36
61
|
export async function executeVideoGeneration(
|
|
37
62
|
input: WizardVideoInput,
|
|
38
63
|
model: string,
|
|
39
64
|
onProgress?: (status: string) => void,
|
|
65
|
+
modelConfig?: VideoModelConfig,
|
|
40
66
|
): Promise<ExecutionResult> {
|
|
41
67
|
const { providerRegistry } = await import("../../../../../infrastructure/services/provider-registry.service");
|
|
42
68
|
|
|
@@ -50,47 +76,19 @@ export async function executeVideoGeneration(
|
|
|
50
76
|
}
|
|
51
77
|
|
|
52
78
|
try {
|
|
53
|
-
const sourceImage = formatBase64(input.sourceImageBase64);
|
|
54
|
-
|
|
55
79
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
56
80
|
console.log("[VideoExecutor] Generation starting", {
|
|
57
81
|
model,
|
|
82
|
+
hasModelConfig: !!modelConfig,
|
|
58
83
|
duration: input.duration,
|
|
59
|
-
aspectRatio: input.aspectRatio,
|
|
60
84
|
resolution: input.resolution,
|
|
61
85
|
});
|
|
62
86
|
}
|
|
63
87
|
|
|
64
|
-
//
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
prompt: input.prompt,
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
if (sourceImage && sourceImage.length > 0) {
|
|
72
|
-
modelInput.image_url = sourceImage;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (isSubjectReference) {
|
|
76
|
-
modelInput.prompt_optimizer = true;
|
|
77
|
-
if (input.duration) {
|
|
78
|
-
modelInput.duration = String(input.duration);
|
|
79
|
-
}
|
|
80
|
-
if (input.resolution) {
|
|
81
|
-
modelInput.resolution = input.resolution;
|
|
82
|
-
}
|
|
83
|
-
} else {
|
|
84
|
-
if (input.duration) {
|
|
85
|
-
modelInput.duration = input.duration;
|
|
86
|
-
}
|
|
87
|
-
if (input.aspectRatio) {
|
|
88
|
-
modelInput.aspect_ratio = input.aspectRatio;
|
|
89
|
-
}
|
|
90
|
-
if (input.resolution) {
|
|
91
|
-
modelInput.resolution = input.resolution;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
88
|
+
// Use modelConfig.buildInput() if available, otherwise generic fallback
|
|
89
|
+
const modelInput = modelConfig
|
|
90
|
+
? modelConfig.buildInput(input)
|
|
91
|
+
: buildGenericInput(input);
|
|
94
92
|
|
|
95
93
|
let lastStatus = "";
|
|
96
94
|
const result = await provider.subscribe(model, modelInput, {
|
|
@@ -126,6 +124,7 @@ export async function executeVideoGeneration(
|
|
|
126
124
|
export async function submitVideoGenerationToQueue(
|
|
127
125
|
input: WizardVideoInput,
|
|
128
126
|
model: string,
|
|
127
|
+
modelConfig?: VideoModelConfig,
|
|
129
128
|
): Promise<SubmissionResult> {
|
|
130
129
|
const { providerRegistry } = await import("../../../../../infrastructure/services/provider-registry.service");
|
|
131
130
|
|
|
@@ -139,37 +138,10 @@ export async function submitVideoGenerationToQueue(
|
|
|
139
138
|
}
|
|
140
139
|
|
|
141
140
|
try {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
const modelInput: Record<string, unknown> = {
|
|
147
|
-
prompt: input.prompt,
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
if (sourceImage && sourceImage.length > 0) {
|
|
151
|
-
modelInput.image_url = sourceImage;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
if (isSubjectReference) {
|
|
155
|
-
modelInput.prompt_optimizer = true;
|
|
156
|
-
if (input.duration) {
|
|
157
|
-
modelInput.duration = String(input.duration);
|
|
158
|
-
}
|
|
159
|
-
if (input.resolution) {
|
|
160
|
-
modelInput.resolution = input.resolution;
|
|
161
|
-
}
|
|
162
|
-
} else {
|
|
163
|
-
if (input.duration) {
|
|
164
|
-
modelInput.duration = input.duration;
|
|
165
|
-
}
|
|
166
|
-
if (input.aspectRatio) {
|
|
167
|
-
modelInput.aspect_ratio = input.aspectRatio;
|
|
168
|
-
}
|
|
169
|
-
if (input.resolution) {
|
|
170
|
-
modelInput.resolution = input.resolution;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
141
|
+
// Use modelConfig.buildInput() if available, otherwise generic fallback
|
|
142
|
+
const modelInput = modelConfig
|
|
143
|
+
? modelConfig.buildInput(input)
|
|
144
|
+
: buildGenericInput(input);
|
|
173
145
|
|
|
174
146
|
const submission = await provider.submitJob(model, modelInput);
|
|
175
147
|
|
package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts
CHANGED
|
@@ -67,7 +67,7 @@ export async function buildVideoInput(
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export function createVideoStrategy(options: CreateVideoStrategyOptions): WizardStrategy {
|
|
70
|
-
const { scenario, creditCost } = options;
|
|
70
|
+
const { scenario, modelConfig, creditCost } = options;
|
|
71
71
|
|
|
72
72
|
// Validate model early - fail fast
|
|
73
73
|
if (!scenario.model) {
|
|
@@ -78,10 +78,9 @@ export function createVideoStrategy(options: CreateVideoStrategyOptions): Wizard
|
|
|
78
78
|
|
|
79
79
|
return {
|
|
80
80
|
execute: async (input: unknown) => {
|
|
81
|
-
// Runtime validation with descriptive errors
|
|
82
81
|
const videoInput = validateWizardVideoInput(input);
|
|
83
82
|
|
|
84
|
-
const result = await executeVideoGeneration(videoInput, model);
|
|
83
|
+
const result = await executeVideoGeneration(videoInput, model, undefined, modelConfig);
|
|
85
84
|
|
|
86
85
|
if (!result.success || !result.videoUrl) {
|
|
87
86
|
throw new Error(result.error || "Video generation failed");
|
|
@@ -91,10 +90,9 @@ export function createVideoStrategy(options: CreateVideoStrategyOptions): Wizard
|
|
|
91
90
|
},
|
|
92
91
|
|
|
93
92
|
submitToQueue: async (input: unknown) => {
|
|
94
|
-
// Runtime validation with descriptive errors
|
|
95
93
|
const videoInput = validateWizardVideoInput(input);
|
|
96
94
|
|
|
97
|
-
const result = await submitVideoGenerationToQueue(videoInput, model);
|
|
95
|
+
const result = await submitVideoGenerationToQueue(videoInput, model, modelConfig);
|
|
98
96
|
|
|
99
97
|
return {
|
|
100
98
|
success: result.success,
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
|
|
7
7
|
import type { ScenarioInputType } from "../../../../scenarios/domain/Scenario";
|
|
8
|
+
import type { VideoModelConfig } from "../../../../../domain/interfaces/video-model-config.types";
|
|
8
9
|
|
|
9
10
|
export interface WizardVideoInput {
|
|
10
11
|
/** Source image (optional for text-to-video) */
|
|
@@ -26,6 +27,8 @@ export interface WizardVideoResult {
|
|
|
26
27
|
|
|
27
28
|
export interface CreateVideoStrategyOptions {
|
|
28
29
|
readonly scenario: WizardScenarioData;
|
|
30
|
+
/** Model configuration - encapsulates all model-specific behavior */
|
|
31
|
+
readonly modelConfig?: VideoModelConfig;
|
|
29
32
|
readonly collectionName?: string;
|
|
30
33
|
/** Credit cost for this generation - REQUIRED, determined by the app */
|
|
31
34
|
readonly creditCost: number;
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import type { WizardScenarioData } from "../../presentation/hooks/useWizardGeneration";
|
|
8
8
|
import type { WizardStrategy } from "./wizard-strategy.types";
|
|
9
|
+
import type { VideoModelConfig } from "../../../../../domain/interfaces/video-model-config.types";
|
|
9
10
|
import { createImageStrategy, buildImageInput } from "./image-generation.strategy";
|
|
10
11
|
import { createVideoStrategy, buildVideoInput } from "./video-generation.strategy";
|
|
11
12
|
|
|
@@ -17,6 +18,8 @@ export type { WizardStrategy } from "./wizard-strategy.types";
|
|
|
17
18
|
|
|
18
19
|
export interface CreateWizardStrategyOptions {
|
|
19
20
|
readonly scenario: WizardScenarioData;
|
|
21
|
+
/** Model configuration - encapsulates all model-specific behavior */
|
|
22
|
+
readonly modelConfig?: VideoModelConfig;
|
|
20
23
|
readonly collectionName?: string;
|
|
21
24
|
/** Credit cost for this generation - REQUIRED, determined by the app */
|
|
22
25
|
readonly creditCost: number;
|
|
@@ -27,14 +30,14 @@ export interface CreateWizardStrategyOptions {
|
|
|
27
30
|
// ============================================================================
|
|
28
31
|
|
|
29
32
|
export function createWizardStrategy(options: CreateWizardStrategyOptions): WizardStrategy {
|
|
30
|
-
const { scenario, collectionName, creditCost } = options;
|
|
33
|
+
const { scenario, modelConfig, collectionName, creditCost } = options;
|
|
31
34
|
|
|
32
35
|
if (scenario.outputType === "image") {
|
|
33
36
|
return createImageStrategy({ scenario, collectionName, creditCost });
|
|
34
37
|
}
|
|
35
38
|
|
|
36
39
|
// Default to video strategy for video outputType or undefined
|
|
37
|
-
return createVideoStrategy({ scenario, collectionName, creditCost });
|
|
40
|
+
return createVideoStrategy({ scenario, modelConfig, collectionName, creditCost });
|
|
38
41
|
}
|
|
39
42
|
|
|
40
43
|
// ============================================================================
|
|
@@ -11,6 +11,7 @@ import type { WizardScenarioData } from "../hooks/useWizardGeneration";
|
|
|
11
11
|
import type { AlertMessages } from "../../../../../presentation/hooks/generation/types";
|
|
12
12
|
import type { GenerationErrorInfo } from "./WizardFlow.types";
|
|
13
13
|
import type { CreditCalculatorFn } from "../../domain/types/credit-calculation.types";
|
|
14
|
+
import type { VideoModelConfig } from "../../../../../domain/interfaces/video-model-config.types";
|
|
14
15
|
import { validateScenario } from "../utilities/validateScenario";
|
|
15
16
|
import { WizardFlowContent } from "./WizardFlowContent";
|
|
16
17
|
import {
|
|
@@ -24,6 +25,8 @@ export interface GenericWizardFlowProps {
|
|
|
24
25
|
readonly featureConfig: WizardFeatureConfig;
|
|
25
26
|
readonly scenario?: WizardScenarioData;
|
|
26
27
|
readonly scenarioId?: string;
|
|
28
|
+
/** Model configuration - encapsulates all model-specific behavior */
|
|
29
|
+
readonly modelConfig?: VideoModelConfig;
|
|
27
30
|
readonly userId?: string;
|
|
28
31
|
readonly alertMessages: AlertMessages;
|
|
29
32
|
/** Credit cost for this generation - REQUIRED, determined by the app */
|
|
@@ -54,6 +57,7 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = (props) => {
|
|
|
54
57
|
featureConfig,
|
|
55
58
|
scenario: scenarioProp,
|
|
56
59
|
scenarioId,
|
|
60
|
+
modelConfig,
|
|
57
61
|
userId,
|
|
58
62
|
alertMessages,
|
|
59
63
|
creditCost,
|
|
@@ -111,6 +115,7 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = (props) => {
|
|
|
111
115
|
featureConfig={featureConfig}
|
|
112
116
|
scenario={scenario}
|
|
113
117
|
validatedScenario={validatedScenario}
|
|
118
|
+
modelConfig={modelConfig}
|
|
114
119
|
userId={userId}
|
|
115
120
|
alertMessages={alertMessages}
|
|
116
121
|
creditCost={creditCost}
|
|
@@ -20,11 +20,14 @@ import { extractDuration, extractResolution } from "../../infrastructure/utils/c
|
|
|
20
20
|
import { WizardStepRenderer } from "./WizardStepRenderer";
|
|
21
21
|
import { StarRatingPicker } from "../../../../result-preview/presentation/components/StarRatingPicker";
|
|
22
22
|
import type { CreditCalculatorFn } from "../../domain/types/credit-calculation.types";
|
|
23
|
+
import type { VideoModelConfig } from "../../../../../domain/interfaces/video-model-config.types";
|
|
23
24
|
|
|
24
25
|
export interface WizardFlowContentProps {
|
|
25
26
|
readonly featureConfig: WizardFeatureConfig;
|
|
26
27
|
readonly scenario?: WizardScenarioData;
|
|
27
28
|
readonly validatedScenario: WizardScenarioData;
|
|
29
|
+
/** Model configuration - encapsulates all model-specific behavior */
|
|
30
|
+
readonly modelConfig?: VideoModelConfig;
|
|
28
31
|
readonly userId?: string;
|
|
29
32
|
readonly alertMessages: AlertMessages;
|
|
30
33
|
/** Credit cost for this generation - REQUIRED, determined by the app */
|
|
@@ -54,6 +57,7 @@ export const WizardFlowContent: React.FC<WizardFlowContentProps> = (props) => {
|
|
|
54
57
|
featureConfig,
|
|
55
58
|
scenario,
|
|
56
59
|
validatedScenario,
|
|
60
|
+
modelConfig,
|
|
57
61
|
userId,
|
|
58
62
|
alertMessages,
|
|
59
63
|
creditCost, // Still needed for initial feature gate in parent
|
|
@@ -174,6 +178,7 @@ export const WizardFlowContent: React.FC<WizardFlowContentProps> = (props) => {
|
|
|
174
178
|
|
|
175
179
|
useWizardGeneration({
|
|
176
180
|
scenario: validatedScenario,
|
|
181
|
+
modelConfig,
|
|
177
182
|
wizardData: customData,
|
|
178
183
|
userId,
|
|
179
184
|
isGeneratingStep: currentStep?.type === StepType.GENERATING,
|
|
@@ -22,6 +22,7 @@ export type {
|
|
|
22
22
|
export const useWizardGeneration = (props: UseWizardGenerationProps): UseWizardGenerationReturn => {
|
|
23
23
|
const {
|
|
24
24
|
scenario,
|
|
25
|
+
modelConfig,
|
|
25
26
|
wizardData,
|
|
26
27
|
userId,
|
|
27
28
|
isGeneratingStep,
|
|
@@ -43,7 +44,7 @@ export const useWizardGeneration = (props: UseWizardGenerationProps): UseWizardG
|
|
|
43
44
|
}, []);
|
|
44
45
|
|
|
45
46
|
const persistence = useMemo(() => createCreationPersistence(), []);
|
|
46
|
-
const strategy = useMemo(() => createWizardStrategy({ scenario, creditCost }), [scenario, creditCost]);
|
|
47
|
+
const strategy = useMemo(() => createWizardStrategy({ scenario, modelConfig, creditCost }), [scenario, modelConfig, creditCost]);
|
|
47
48
|
const isVideoMode = scenario.outputType === "video" && !!strategy.submitToQueue;
|
|
48
49
|
|
|
49
50
|
const videoGeneration = useVideoQueueGeneration({
|
|
@@ -7,6 +7,39 @@ declare const __DEV__: boolean;
|
|
|
7
7
|
/** Max consecutive transient errors before aborting */
|
|
8
8
|
const MAX_CONSECUTIVE_ERRORS = 5;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Extract meaningful error message from various error formats.
|
|
12
|
+
* Fal AI client throws ValidationError with empty .message but details in .body/.detail
|
|
13
|
+
*/
|
|
14
|
+
function extractErrorMessage(err: unknown): string {
|
|
15
|
+
if (!err) return "Generation failed";
|
|
16
|
+
|
|
17
|
+
// Standard Error with message
|
|
18
|
+
if (err instanceof Error && err.message && err.message.length > 0) {
|
|
19
|
+
return err.message;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Fal AI ValidationError - has .body.detail array
|
|
23
|
+
const errObj = err as Record<string, unknown>;
|
|
24
|
+
if (errObj.body && typeof errObj.body === "object") {
|
|
25
|
+
const body = errObj.body as Record<string, unknown>;
|
|
26
|
+
if (Array.isArray(body.detail) && body.detail.length > 0) {
|
|
27
|
+
const first = body.detail[0] as { msg?: string; type?: string } | undefined;
|
|
28
|
+
if (first?.msg) return first.msg;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Direct .detail array on error object
|
|
33
|
+
if (Array.isArray(errObj.detail) && errObj.detail.length > 0) {
|
|
34
|
+
const first = errObj.detail[0] as { msg?: string } | undefined;
|
|
35
|
+
if (first?.msg) return first.msg;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Fallback to string conversion
|
|
39
|
+
const str = String(err);
|
|
40
|
+
return str.length > 0 && str !== "[object Object]" ? str : "Generation failed";
|
|
41
|
+
}
|
|
42
|
+
|
|
10
43
|
interface PollParams {
|
|
11
44
|
requestId: string;
|
|
12
45
|
model: string;
|
|
@@ -56,7 +89,7 @@ export const pollQueueStatus = async (params: PollParams): Promise<void> => {
|
|
|
56
89
|
}
|
|
57
90
|
await onComplete(urls);
|
|
58
91
|
} catch (resultErr) {
|
|
59
|
-
const errorMessage = resultErr
|
|
92
|
+
const errorMessage = extractErrorMessage(resultErr);
|
|
60
93
|
if (__DEV__) {
|
|
61
94
|
console.error("[VideoQueueGeneration] ❌ Result error:", errorMessage);
|
|
62
95
|
console.error("[VideoQueueGeneration] ❌ Full error:", resultErr);
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
import type { AlertMessages } from "../../../../../presentation/hooks/generation/types";
|
|
7
7
|
import type { ScenarioInputType, ScenarioPromptType } from "../../../../scenarios/domain/Scenario";
|
|
8
|
+
import type { VideoModelConfig } from "../../../../../domain/interfaces/video-model-config.types";
|
|
8
9
|
|
|
9
10
|
export type WizardOutputType = "image" | "video";
|
|
10
11
|
|
|
@@ -26,6 +27,8 @@ export interface WizardScenarioData {
|
|
|
26
27
|
|
|
27
28
|
export interface UseWizardGenerationProps {
|
|
28
29
|
readonly scenario: WizardScenarioData;
|
|
30
|
+
/** Model configuration - encapsulates all model-specific behavior */
|
|
31
|
+
readonly modelConfig?: VideoModelConfig;
|
|
29
32
|
readonly wizardData: Record<string, unknown>;
|
|
30
33
|
readonly userId?: string;
|
|
31
34
|
readonly isGeneratingStep: boolean;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Build Wizard Config from VideoModelConfig
|
|
3
|
+
* Generates wizard step configuration from model capabilities.
|
|
4
|
+
* No hardcoded model-specific values - everything comes from config.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { VideoModelConfig } from "../../../../domain/interfaces/video-model-config.types";
|
|
8
|
+
import type { WizardFeatureConfig } from "../domain/entities/wizard-feature-config.types";
|
|
9
|
+
import type { WizardStepConfig } from "../domain/entities/wizard-step.types";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Builds a WizardFeatureConfig from a VideoModelConfig + input steps.
|
|
13
|
+
* Input steps (photo upload, text input) come from the caller.
|
|
14
|
+
* Resolution/duration/aspectRatio steps are auto-generated from model capabilities.
|
|
15
|
+
*/
|
|
16
|
+
export function buildWizardConfigFromModelConfig(
|
|
17
|
+
modelConfig: VideoModelConfig,
|
|
18
|
+
inputSteps: readonly WizardStepConfig[],
|
|
19
|
+
): WizardFeatureConfig {
|
|
20
|
+
const steps: WizardStepConfig[] = [...inputSteps];
|
|
21
|
+
|
|
22
|
+
if (modelConfig.capabilities.resolutions.length > 0) {
|
|
23
|
+
steps.push({
|
|
24
|
+
id: "resolution",
|
|
25
|
+
type: "selection" as const,
|
|
26
|
+
titleKey: "generation.resolution.title",
|
|
27
|
+
selectionType: "resolution" as const,
|
|
28
|
+
options: modelConfig.capabilities.resolutions.map((r) => ({
|
|
29
|
+
id: r.id,
|
|
30
|
+
label: r.label,
|
|
31
|
+
value: r.value,
|
|
32
|
+
})),
|
|
33
|
+
required: true,
|
|
34
|
+
defaultValue: modelConfig.capabilities.defaults.resolution,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (modelConfig.capabilities.aspectRatios && modelConfig.capabilities.aspectRatios.length > 0) {
|
|
39
|
+
steps.push({
|
|
40
|
+
id: "aspect_ratio",
|
|
41
|
+
type: "selection" as const,
|
|
42
|
+
titleKey: "generation.aspectRatio.title",
|
|
43
|
+
selectionType: "aspect_ratio" as const,
|
|
44
|
+
options: modelConfig.capabilities.aspectRatios.map((a) => ({
|
|
45
|
+
id: a.id,
|
|
46
|
+
label: a.label,
|
|
47
|
+
value: a.value,
|
|
48
|
+
})),
|
|
49
|
+
required: true,
|
|
50
|
+
defaultValue: modelConfig.capabilities.defaults.aspectRatio,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (modelConfig.capabilities.durations.length > 0) {
|
|
55
|
+
steps.push({
|
|
56
|
+
id: "duration",
|
|
57
|
+
type: "selection" as const,
|
|
58
|
+
titleKey: "generation.duration.title",
|
|
59
|
+
selectionType: "duration" as const,
|
|
60
|
+
layout: "list" as const,
|
|
61
|
+
options: modelConfig.capabilities.durations.map((d) => ({
|
|
62
|
+
id: d.id,
|
|
63
|
+
label: d.label,
|
|
64
|
+
value: d.value,
|
|
65
|
+
})),
|
|
66
|
+
required: true,
|
|
67
|
+
defaultValue: modelConfig.capabilities.defaults.duration,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
id: modelConfig.modelId,
|
|
73
|
+
name: modelConfig.displayName,
|
|
74
|
+
steps,
|
|
75
|
+
};
|
|
76
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -40,3 +40,9 @@ export {
|
|
|
40
40
|
getCreditConfig,
|
|
41
41
|
} from "./domains/generation/wizard";
|
|
42
42
|
export type { ValidationResult, CreditCalculatorFn } from "./domains/generation/wizard";
|
|
43
|
+
|
|
44
|
+
// Video Model Config (for app-side model definitions)
|
|
45
|
+
export type { VideoModelConfig, ModelCapabilityOption } from "./domain/interfaces";
|
|
46
|
+
|
|
47
|
+
// Wizard Config Builder (generates wizard steps from VideoModelConfig)
|
|
48
|
+
export { buildWizardConfigFromModelConfig } from "./domains/generation/wizard/utilities/build-wizard-config";
|