@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.77.0",
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
+ }
@@ -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
- return await readFileAsBase64(uri);
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);
@@ -1,10 +1,12 @@
1
1
  /**
2
2
  * Video Generation Executor
3
- * Handles the actual video generation execution
4
- * Uses direct provider calls for text-to-video and image-to-video generation models
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
- // Detect Minimax subject reference model
65
- const isSubjectReference = model.includes("minimax") || model.includes("hailuo");
66
-
67
- const modelInput: Record<string, unknown> = {
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
- const sourceImage = formatBase64(input.sourceImageBase64);
143
-
144
- const isSubjectReference = model.includes("minimax") || model.includes("hailuo");
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
 
@@ -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 instanceof Error ? resultErr.message : "Generation failed";
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";