@umituz/react-native-ai-generation-content 1.84.4 → 1.84.5

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.84.4",
3
+ "version": "1.84.5",
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",
@@ -84,6 +84,7 @@
84
84
  "expo-clipboard": "^8.0.8",
85
85
  "expo-crypto": "^15.0.8",
86
86
  "expo-device": "^8.0.10",
87
+ "expo-document-picker": "^14.0.8",
87
88
  "expo-file-system": "^19.0.21",
88
89
  "expo-font": "^14.0.10",
89
90
  "expo-haptics": "^15.0.8",
@@ -17,6 +17,7 @@ export enum StepType {
17
17
  PARTNER_UPLOAD = "partner_upload",
18
18
  TEXT_INPUT = "text_input",
19
19
  FEATURE_SELECTION = "feature_selection",
20
+ AUDIO_PICKER = "audio_picker",
20
21
  // Generation steps
21
22
  GENERATING = "generating",
22
23
  RESULT_PREVIEW = "result_preview",
@@ -6,3 +6,4 @@
6
6
  export { TEXT_TO_IMAGE_WIZARD_CONFIG } from "./text-to-image.config";
7
7
  export { TEXT_TO_VIDEO_WIZARD_CONFIG } from "./text-to-video.config";
8
8
  export { IMAGE_TO_VIDEO_WIZARD_CONFIG } from "./image-to-video.config";
9
+ export { SOLO_VIDEO_WIZARD_CONFIG } from "./solo-video.config";
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Solo Video Wizard Config
3
+ * Flow: Photo → Prompt (with info about two-step generation) → Audio (optional) → Generation
4
+ *
5
+ * Two-step generation:
6
+ * 1. Generate image from photo + prompt (nano-banana-2/edit)
7
+ * 2. Generate video from that image (I2V model)
8
+ */
9
+
10
+ import type { WizardFeatureConfig } from "../domain/entities/wizard-feature.types";
11
+
12
+ export const SOLO_VIDEO_WIZARD_CONFIG: WizardFeatureConfig = {
13
+ id: "solo-video",
14
+ name: "Solo Video",
15
+ steps: [
16
+ {
17
+ id: "photo_1",
18
+ type: "photo_upload",
19
+ titleKey: "soloVideo.selectPhoto",
20
+ subtitleKey: "soloVideo.selectPhotoHint",
21
+ showFaceDetection: false,
22
+ showPhotoTips: true,
23
+ required: true,
24
+ },
25
+ {
26
+ id: "video_prompt",
27
+ type: "text_input",
28
+ titleKey: "soloVideo.prompt",
29
+ subtitleKey: "soloVideo.promptInfo",
30
+ placeholderKey: "soloVideo.promptPlaceholder",
31
+ required: true,
32
+ minLength: 3,
33
+ maxLength: 500,
34
+ multiline: true,
35
+ },
36
+ {
37
+ id: "background_audio",
38
+ type: "audio_picker",
39
+ titleKey: "soloVideo.audioTitle",
40
+ subtitleKey: "soloVideo.audioSubtitle",
41
+ required: false,
42
+ maxFileSizeMB: 20,
43
+ },
44
+ ],
45
+ };
@@ -84,6 +84,17 @@ export interface CreditGateStepConfig extends BaseStepConfig {
84
84
  readonly messageKey?: string;
85
85
  }
86
86
 
87
+ /**
88
+ * Audio Picker Step Configuration
89
+ */
90
+ export interface AudioPickerStepConfig extends BaseStepConfig {
91
+ readonly type: "audio_picker";
92
+ /** Allowed MIME types (default: audio/mpeg, audio/mp4, audio/wav, audio/aac) */
93
+ readonly allowedTypes?: readonly string[];
94
+ /** Max file size in MB (default: 20) */
95
+ readonly maxFileSizeMB?: number;
96
+ }
97
+
87
98
  /**
88
99
  * Union of all step config types
89
100
  */
@@ -94,4 +105,5 @@ export type WizardStepConfig =
94
105
  | TextInputStepConfig
95
106
  | SelectionStepConfig
96
107
  | PreviewStepConfig
108
+ | AudioPickerStepConfig
97
109
  | BaseStepConfig;
@@ -13,6 +13,7 @@ export type {
13
13
  TextInputStepConfig,
14
14
  SelectionStepConfig,
15
15
  PreviewStepConfig,
16
+ AudioPickerStepConfig,
16
17
  WizardStepConfig,
17
18
  } from "./domain/entities/wizard-step.types";
18
19
 
@@ -70,11 +71,13 @@ export { GenericWizardFlow } from "./presentation/components";
70
71
  export type { GenericWizardFlowProps } from "./presentation/components";
71
72
 
72
73
  // Presentation - Screens
73
- export { GeneratingScreen, TextInputScreen } from "./presentation/screens";
74
+ export { GeneratingScreen, TextInputScreen, AudioPickerScreen } from "./presentation/screens";
74
75
  export type {
75
76
  TextInputScreenTranslations,
76
77
  TextInputScreenConfig,
77
78
  TextInputScreenProps,
79
+ AudioPickerScreenTranslations,
80
+ AudioPickerScreenProps,
78
81
  } from "./presentation/screens";
79
82
 
80
83
  // Feature Configs
@@ -82,4 +85,5 @@ export {
82
85
  TEXT_TO_IMAGE_WIZARD_CONFIG,
83
86
  TEXT_TO_VIDEO_WIZARD_CONFIG,
84
87
  IMAGE_TO_VIDEO_WIZARD_CONFIG,
88
+ SOLO_VIDEO_WIZARD_CONFIG,
85
89
  } from "./configs";
@@ -20,6 +20,7 @@ const convertToFlowStep = (wizardStep: WizardStepConfig): StepDefinition => {
20
20
  text_input: StepType.TEXT_INPUT,
21
21
  selection: StepType.FEATURE_SELECTION,
22
22
  preview: StepType.SCENARIO_PREVIEW,
23
+ audio_picker: StepType.AUDIO_PICKER,
23
24
  };
24
25
 
25
26
  return {
@@ -12,6 +12,7 @@ import { renderPreviewStep } from "./step-renderers/renderPreviewStep";
12
12
  import { renderPhotoUploadStep } from "./step-renderers/renderPhotoUploadStep";
13
13
  import { renderTextInputStep } from "./step-renderers/renderTextInputStep";
14
14
  import { renderSelectionStep } from "./step-renderers/renderSelectionStep";
15
+ import { renderAudioPickerStep } from "./step-renderers/renderAudioPickerStep";
15
16
  import type { WizardStepRendererProps } from "./WizardStepRenderer.types";
16
17
 
17
18
  export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
@@ -97,6 +98,9 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
97
98
  case StepType.FEATURE_SELECTION:
98
99
  return renderSelectionStep({ key: step.id, step, customData, onBack, onPhotoContinue, calculateCreditForSelection, t, creditCost });
99
100
 
101
+ case StepType.AUDIO_PICKER:
102
+ return renderAudioPickerStep({ key: step.id, step, onBack, onPhotoContinue, t, creditCost });
103
+
100
104
  default:
101
105
  if (typeof __DEV__ !== "undefined" && __DEV__) {
102
106
  console.warn("[WizardStepRenderer] Unhandled step type", { stepType: step.type });
@@ -2,6 +2,7 @@ import type {
2
2
  WizardStepConfig,
3
3
  TextInputStepConfig,
4
4
  SelectionStepConfig,
5
+ AudioPickerStepConfig,
5
6
  } from "../../domain/entities/wizard-step.types";
6
7
  import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
7
8
 
@@ -51,3 +52,9 @@ export function getTextInputValue(data: unknown): string | undefined {
51
52
  if (isRecord(data) && "text" in data) return String(data.text);
52
53
  return undefined;
53
54
  }
55
+
56
+ export function getAudioPickerConfig(config: unknown): AudioPickerStepConfig | undefined {
57
+ if (!isRecord(config)) return undefined;
58
+ if (config.type === "audio_picker") return config as unknown as AudioPickerStepConfig;
59
+ return undefined;
60
+ }
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Audio Picker Step Renderer
3
+ */
4
+
5
+ import React from "react";
6
+ import { AudioPickerScreen } from "../../screens/AudioPickerScreen";
7
+ import { getAudioPickerConfig } from "../WizardStepRenderer.utils";
8
+ import type { StepDefinition } from "../../../../../../domain/entities/flow-config.types";
9
+ import type { UploadedImage } from "../../../../../../presentation/hooks/generation/useAIGenerateState";
10
+
11
+ interface AudioPickerStepProps {
12
+ readonly key?: string;
13
+ readonly step: StepDefinition;
14
+ readonly onBack: () => void;
15
+ readonly onPhotoContinue: (stepId: string, image: UploadedImage) => void;
16
+ readonly t: (key: string) => string;
17
+ readonly creditCost?: number;
18
+ }
19
+
20
+ export function renderAudioPickerStep({
21
+ step,
22
+ onBack,
23
+ onPhotoContinue,
24
+ t,
25
+ creditCost,
26
+ }: AudioPickerStepProps): React.ReactElement {
27
+ const audioConfig = getAudioPickerConfig(step.config);
28
+ const titleKey = audioConfig?.titleKey ?? `wizard.steps.${step.id}.title`;
29
+ const subtitleKey = audioConfig?.subtitleKey ?? `wizard.steps.${step.id}.subtitle`;
30
+ const isOptional = !(step.required ?? true);
31
+
32
+ return (
33
+ <AudioPickerScreen
34
+ key={step.id}
35
+ stepId={step.id}
36
+ translations={{
37
+ title: t(titleKey),
38
+ subtitle: subtitleKey ? t(subtitleKey) : undefined,
39
+ selectButton: t("audioPicker.selectFile"),
40
+ skipButton: t("audioPicker.skip"),
41
+ continueButton: t("common.continue"),
42
+ selectedLabel: t("audioPicker.selected"),
43
+ fileTooLarge: t("common.errors.file_too_large"),
44
+ unsupportedFormat: t("audioPicker.unsupportedFormat"),
45
+ }}
46
+ allowedTypes={audioConfig?.allowedTypes as string[] | undefined}
47
+ maxFileSizeMB={audioConfig?.maxFileSizeMB}
48
+ optional={isOptional}
49
+ creditCost={creditCost}
50
+ onBack={onBack}
51
+ onContinue={(audioUri) => {
52
+ onPhotoContinue(step.id, { uri: audioUri, previewUrl: "" } as UploadedImage);
53
+ }}
54
+ />
55
+ );
56
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * AudioPickerScreen
3
+ * Allows users to pick an audio file (mp3, m4a, wav) for video generation.
4
+ * Supports optional skip. Uses expo-document-picker.
5
+ */
6
+
7
+ import React, { useState, useCallback, useMemo } from "react";
8
+ import { View, StyleSheet } from "react-native";
9
+ import * as DocumentPicker from "expo-document-picker";
10
+ import { AtomicText, AtomicButton } from "@umituz/react-native-design-system/atoms";
11
+ import { ScreenLayout } from "@umituz/react-native-design-system/layouts";
12
+ import { NavigationHeader } from "@umituz/react-native-design-system/molecules";
13
+ import { useAppDesignTokens, type DesignTokens } from "@umituz/react-native-design-system/theme";
14
+ import { WizardContinueButton } from "../components/WizardContinueButton";
15
+ import type { AudioPickerScreenProps } from "./AudioPickerScreen.types";
16
+
17
+ export type {
18
+ AudioPickerScreenTranslations,
19
+ AudioPickerScreenProps,
20
+ } from "./AudioPickerScreen.types";
21
+
22
+ const DEFAULT_AUDIO_TYPES = [
23
+ "audio/mpeg",
24
+ "audio/mp4",
25
+ "audio/wav",
26
+ "audio/aac",
27
+ "audio/x-m4a",
28
+ ];
29
+
30
+ const DEFAULT_MAX_SIZE_MB = 20;
31
+
32
+ export const AudioPickerScreen: React.FC<AudioPickerScreenProps> = ({
33
+ stepId: _stepId,
34
+ translations,
35
+ allowedTypes,
36
+ maxFileSizeMB,
37
+ optional = true,
38
+ creditCost,
39
+ onBack,
40
+ onContinue,
41
+ }) => {
42
+ const tokens = useAppDesignTokens();
43
+ const [selectedFile, setSelectedFile] = useState<{
44
+ uri: string;
45
+ name: string;
46
+ size?: number;
47
+ } | null>(null);
48
+ const [error, setError] = useState<string | null>(null);
49
+
50
+ const maxSize = (maxFileSizeMB ?? DEFAULT_MAX_SIZE_MB) * 1024 * 1024;
51
+ const mimeTypes = allowedTypes ?? DEFAULT_AUDIO_TYPES;
52
+
53
+ const handlePick = useCallback(async () => {
54
+ try {
55
+ setError(null);
56
+ const result = await DocumentPicker.getDocumentAsync({
57
+ type: mimeTypes as string[],
58
+ copyToCacheDirectory: true,
59
+ });
60
+
61
+ if (result.canceled || !result.assets?.length) return;
62
+
63
+ const asset = result.assets[0];
64
+
65
+ if (asset.size && asset.size > maxSize) {
66
+ setError(
67
+ translations.fileTooLarge ??
68
+ `File too large. Max ${maxFileSizeMB ?? DEFAULT_MAX_SIZE_MB}MB.`
69
+ );
70
+ return;
71
+ }
72
+
73
+ setSelectedFile({
74
+ uri: asset.uri,
75
+ name: asset.name,
76
+ size: asset.size ?? undefined,
77
+ });
78
+ } catch {
79
+ if (__DEV__) {
80
+ console.warn("[AudioPickerScreen] Failed to pick document");
81
+ }
82
+ }
83
+ }, [mimeTypes, maxSize, maxFileSizeMB, translations]);
84
+
85
+ const handleContinue = useCallback(() => {
86
+ onContinue(selectedFile?.uri ?? "");
87
+ }, [selectedFile, onContinue]);
88
+
89
+ const handleSkip = useCallback(() => {
90
+ onContinue("");
91
+ }, [onContinue]);
92
+
93
+ const formatFileSize = useCallback((bytes?: number) => {
94
+ if (!bytes) return "";
95
+ if (bytes < 1024) return `${bytes} B`;
96
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
97
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
98
+ }, []);
99
+
100
+ const canContinue = !!selectedFile || optional;
101
+ const styles = useMemo(() => createStyles(tokens), [tokens]);
102
+
103
+ return (
104
+ <View style={{ flex: 1, backgroundColor: tokens.colors.backgroundPrimary }}>
105
+ <NavigationHeader
106
+ title=""
107
+ onBackPress={onBack}
108
+ rightElement={
109
+ <WizardContinueButton
110
+ label={translations.continueButton}
111
+ canContinue={canContinue}
112
+ onPress={handleContinue}
113
+ creditCost={creditCost}
114
+ />
115
+ }
116
+ />
117
+ <ScreenLayout
118
+ scrollable={true}
119
+ edges={["left", "right"]}
120
+ hideScrollIndicator={true}
121
+ contentContainerStyle={styles.scrollContent}
122
+ >
123
+ <AtomicText type="headlineMedium" color="textPrimary" style={styles.title}>
124
+ {translations.title}
125
+ </AtomicText>
126
+
127
+ {translations.subtitle ? (
128
+ <AtomicText type="bodyMedium" color="textSecondary" style={styles.subtitle}>
129
+ {translations.subtitle}
130
+ </AtomicText>
131
+ ) : null}
132
+
133
+ {selectedFile ? (
134
+ <View style={styles.selectedContainer}>
135
+ <AtomicText type="labelLarge" color="textPrimary" style={styles.fileName}>
136
+ {selectedFile.name}
137
+ </AtomicText>
138
+ {selectedFile.size ? (
139
+ <AtomicText type="bodySmall" color="textTertiary">
140
+ {formatFileSize(selectedFile.size)}
141
+ </AtomicText>
142
+ ) : null}
143
+ <AtomicButton
144
+ variant="outline"
145
+ size="sm"
146
+ onPress={handlePick}
147
+ style={styles.changeButton}
148
+ >
149
+ {translations.selectButton}
150
+ </AtomicButton>
151
+ </View>
152
+ ) : (
153
+ <AtomicButton
154
+ variant="outline"
155
+ size="md"
156
+ onPress={handlePick}
157
+ style={styles.pickButton}
158
+ >
159
+ {translations.selectButton}
160
+ </AtomicButton>
161
+ )}
162
+
163
+ {error ? (
164
+ <AtomicText type="bodySmall" color="error" style={styles.error}>
165
+ {error}
166
+ </AtomicText>
167
+ ) : null}
168
+
169
+ {optional && !selectedFile ? (
170
+ <AtomicButton
171
+ variant="ghost"
172
+ size="sm"
173
+ onPress={handleSkip}
174
+ style={styles.skipButton}
175
+ >
176
+ {translations.skipButton}
177
+ </AtomicButton>
178
+ ) : null}
179
+ </ScreenLayout>
180
+ </View>
181
+ );
182
+ };
183
+
184
+ const createStyles = (tokens: DesignTokens) =>
185
+ StyleSheet.create({
186
+ scrollContent: {
187
+ paddingHorizontal: tokens.spacing.lg,
188
+ paddingBottom: 40,
189
+ },
190
+ title: {
191
+ marginBottom: tokens.spacing.sm,
192
+ },
193
+ subtitle: {
194
+ marginBottom: tokens.spacing.xl,
195
+ },
196
+ selectedContainer: {
197
+ backgroundColor: tokens.colors.backgroundSecondary,
198
+ borderRadius: tokens.borders.radius.md,
199
+ borderWidth: 1,
200
+ borderColor: tokens.colors.border,
201
+ padding: tokens.spacing.lg,
202
+ alignItems: "center",
203
+ gap: tokens.spacing.sm,
204
+ },
205
+ fileName: {
206
+ textAlign: "center",
207
+ },
208
+ changeButton: {
209
+ marginTop: tokens.spacing.sm,
210
+ },
211
+ pickButton: {
212
+ marginTop: tokens.spacing.md,
213
+ },
214
+ error: {
215
+ marginTop: tokens.spacing.sm,
216
+ textAlign: "center",
217
+ },
218
+ skipButton: {
219
+ marginTop: tokens.spacing.lg,
220
+ alignSelf: "center",
221
+ },
222
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * AudioPickerScreen Types
3
+ */
4
+
5
+ export interface AudioPickerScreenTranslations {
6
+ readonly title: string;
7
+ readonly subtitle?: string;
8
+ readonly selectButton: string;
9
+ readonly skipButton: string;
10
+ readonly continueButton: string;
11
+ readonly selectedLabel: string;
12
+ readonly fileTooLarge?: string;
13
+ readonly unsupportedFormat?: string;
14
+ }
15
+
16
+ export interface AudioPickerScreenProps {
17
+ readonly stepId: string;
18
+ readonly translations: AudioPickerScreenTranslations;
19
+ /** Allowed MIME types */
20
+ readonly allowedTypes?: readonly string[];
21
+ /** Max file size in MB */
22
+ readonly maxFileSizeMB?: number;
23
+ /** Whether this step can be skipped */
24
+ readonly optional?: boolean;
25
+ /** Calculated credit cost from parent */
26
+ readonly creditCost?: number;
27
+ readonly onBack: () => void;
28
+ /** Called with audio URI, or empty string if skipped */
29
+ readonly onContinue: (audioUri: string) => void;
30
+ }
@@ -5,3 +5,8 @@ export type {
5
5
  TextInputScreenConfig,
6
6
  TextInputScreenProps,
7
7
  } from "./TextInputScreen";
8
+ export { AudioPickerScreen } from "./AudioPickerScreen";
9
+ export type {
10
+ AudioPickerScreenTranslations,
11
+ AudioPickerScreenProps,
12
+ } from "./AudioPickerScreen";
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ export {
29
29
  TEXT_TO_IMAGE_WIZARD_CONFIG,
30
30
  TEXT_TO_VIDEO_WIZARD_CONFIG,
31
31
  IMAGE_TO_VIDEO_WIZARD_CONFIG,
32
+ SOLO_VIDEO_WIZARD_CONFIG,
32
33
  } from "./domains/generation/wizard";
33
34
  export type { WizardScenarioData } from "./domains/generation/wizard";
34
35