@umituz/react-native-ai-generation-content 1.27.7 → 1.27.9

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.
Files changed (85) hide show
  1. package/package.json +1 -1
  2. package/src/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts +3 -30
  3. package/src/domains/generation/wizard/infrastructure/strategies/video-generation.strategy.ts +3 -30
  4. package/src/domains/generation/wizard/infrastructure/strategies/wizard-strategy.constants.ts +0 -2
  5. package/src/domains/scenarios/configs/wizard-configs.ts +28 -28
  6. package/src/domains/scenarios/index.ts +3 -3
  7. package/src/features/image-to-video/README.md +414 -0
  8. package/src/features/image-to-video/domain/constants/animation.constants.ts +47 -0
  9. package/src/features/image-to-video/domain/constants/duration.constants.ts +13 -0
  10. package/src/features/image-to-video/domain/constants/form.constants.ts +22 -0
  11. package/src/features/image-to-video/domain/constants/index.ts +23 -0
  12. package/src/features/image-to-video/domain/constants/music.constants.ts +53 -0
  13. package/src/features/image-to-video/domain/index.ts +5 -0
  14. package/src/features/image-to-video/domain/types/animation.types.ts +20 -0
  15. package/src/features/image-to-video/domain/types/config.types.ts +56 -0
  16. package/src/features/image-to-video/domain/types/duration.types.ts +11 -0
  17. package/src/features/image-to-video/domain/types/form.types.ts +35 -0
  18. package/src/features/image-to-video/domain/types/image-to-video.types.ts +122 -0
  19. package/src/features/image-to-video/domain/types/index.ts +39 -0
  20. package/src/features/image-to-video/domain/types/music.types.ts +21 -0
  21. package/src/features/image-to-video/index.ts +116 -0
  22. package/src/features/image-to-video/infrastructure/index.ts +1 -0
  23. package/src/features/image-to-video/infrastructure/services/image-to-video-executor.ts +165 -0
  24. package/src/features/image-to-video/infrastructure/services/index.ts +5 -0
  25. package/src/features/image-to-video/presentation/components/AddMoreCard.tsx +52 -0
  26. package/src/features/image-to-video/presentation/components/AnimationStyleSelector.tsx +135 -0
  27. package/src/features/image-to-video/presentation/components/DurationSelector.tsx +110 -0
  28. package/src/features/image-to-video/presentation/components/EmptyGridState.tsx +69 -0
  29. package/src/features/image-to-video/presentation/components/GridImageItem.tsx +64 -0
  30. package/src/features/image-to-video/presentation/components/ImageSelectionGrid.styles.ts +84 -0
  31. package/src/features/image-to-video/presentation/components/ImageSelectionGrid.tsx +77 -0
  32. package/src/features/image-to-video/presentation/components/ImageSelectionGrid.types.ts +18 -0
  33. package/src/features/image-to-video/presentation/components/MusicMoodSelector.tsx +181 -0
  34. package/src/features/image-to-video/presentation/components/index.ts +30 -0
  35. package/src/features/image-to-video/presentation/hooks/index.ts +27 -0
  36. package/src/features/image-to-video/presentation/hooks/useFormState.ts +116 -0
  37. package/src/features/image-to-video/presentation/hooks/useGeneration.ts +85 -0
  38. package/src/features/image-to-video/presentation/hooks/useGenerationExecution.ts +143 -0
  39. package/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.ts +107 -0
  40. package/src/features/image-to-video/presentation/hooks/useImageToVideoForm.ts +119 -0
  41. package/src/features/image-to-video/presentation/hooks/useImageToVideoValidation.ts +46 -0
  42. package/src/features/image-to-video/presentation/index.ts +5 -0
  43. package/src/features/text-to-image/README.md +394 -0
  44. package/src/features/text-to-image/domain/constants/index.ts +25 -0
  45. package/src/features/text-to-image/domain/constants/options.constants.ts +39 -0
  46. package/src/features/text-to-image/domain/constants/styles.constants.ts +34 -0
  47. package/src/features/text-to-image/domain/index.ts +7 -0
  48. package/src/features/text-to-image/domain/types/config.types.ts +75 -0
  49. package/src/features/text-to-image/domain/types/form.types.ts +58 -0
  50. package/src/features/text-to-image/domain/types/index.ts +38 -0
  51. package/src/features/text-to-image/domain/types/text-to-image.types.ts +58 -0
  52. package/src/features/text-to-image/index.ts +116 -0
  53. package/src/features/text-to-image/infrastructure/index.ts +1 -0
  54. package/src/features/text-to-image/infrastructure/services/index.ts +5 -0
  55. package/src/features/text-to-image/infrastructure/services/text-to-image-executor.ts +147 -0
  56. package/src/features/text-to-image/presentation/components/index.ts +30 -0
  57. package/src/features/text-to-image/presentation/hooks/index.ts +30 -0
  58. package/src/features/text-to-image/presentation/hooks/useFormState.ts +103 -0
  59. package/src/features/text-to-image/presentation/hooks/useGeneration.ts +134 -0
  60. package/src/features/text-to-image/presentation/hooks/useTextToImageFeature.ts +111 -0
  61. package/src/features/text-to-image/presentation/hooks/useTextToImageForm.ts +58 -0
  62. package/src/features/text-to-image/presentation/index.ts +7 -0
  63. package/src/features/text-to-video/README.md +412 -0
  64. package/src/features/text-to-video/domain/index.ts +1 -0
  65. package/src/features/text-to-video/domain/types/callback.types.ts +69 -0
  66. package/src/features/text-to-video/domain/types/component.types.ts +106 -0
  67. package/src/features/text-to-video/domain/types/config.types.ts +61 -0
  68. package/src/features/text-to-video/domain/types/index.ts +56 -0
  69. package/src/features/text-to-video/domain/types/request.types.ts +36 -0
  70. package/src/features/text-to-video/domain/types/state.types.ts +53 -0
  71. package/src/features/text-to-video/index.ts +68 -0
  72. package/src/features/text-to-video/infrastructure/index.ts +1 -0
  73. package/src/features/text-to-video/infrastructure/services/index.ts +5 -0
  74. package/src/features/text-to-video/infrastructure/services/text-to-video-executor.ts +141 -0
  75. package/src/features/text-to-video/presentation/components/FrameSelector.tsx +153 -0
  76. package/src/features/text-to-video/presentation/components/GenerationTabs.tsx +73 -0
  77. package/src/features/text-to-video/presentation/components/HeroSection.tsx +61 -0
  78. package/src/features/text-to-video/presentation/components/HintCarousel.tsx +96 -0
  79. package/src/features/text-to-video/presentation/components/OptionsPanel.tsx +121 -0
  80. package/src/features/text-to-video/presentation/components/index.ts +10 -0
  81. package/src/features/text-to-video/presentation/hooks/index.ts +17 -0
  82. package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +187 -0
  83. package/src/features/text-to-video/presentation/hooks/useTextToVideoForm.ts +134 -0
  84. package/src/features/text-to-video/presentation/index.ts +7 -0
  85. package/src/index.ts +5 -0
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Image Selection Grid Component
3
+ * Generic component for image selection display
4
+ */
5
+
6
+ import React, { useMemo } from "react";
7
+ import { View, ScrollView } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ useAppDesignTokens,
11
+ } from "@umituz/react-native-design-system";
12
+ import { EmptyGridState } from "./EmptyGridState";
13
+ import { GridImageItem } from "./GridImageItem";
14
+ import { AddMoreCard } from "./AddMoreCard";
15
+ import { createImageSelectionGridStyles } from "./ImageSelectionGrid.styles";
16
+ import type { ImageSelectionGridProps } from "./ImageSelectionGrid.types";
17
+
18
+ export const ImageSelectionGrid: React.FC<ImageSelectionGridProps> = ({
19
+ images,
20
+ maxImages,
21
+ onSelectImages,
22
+ onRemoveImage,
23
+ translations,
24
+ }) => {
25
+ const tokens = useAppDesignTokens();
26
+ const styles = useMemo(
27
+ () => createImageSelectionGridStyles(tokens),
28
+ [tokens]
29
+ );
30
+
31
+ if (images.length === 0) {
32
+ return (
33
+ <EmptyGridState
34
+ styles={styles}
35
+ maxImages={maxImages}
36
+ translations={translations}
37
+ onSelectImages={onSelectImages}
38
+ />
39
+ );
40
+ }
41
+
42
+ return (
43
+ <View style={styles.section}>
44
+ <AtomicText
45
+ type="bodyMedium"
46
+ style={[styles.label, { color: tokens.colors.textPrimary }]}
47
+ >
48
+ {translations.selectedImages} ({images.length}/{maxImages})
49
+ </AtomicText>
50
+ <ScrollView
51
+ horizontal
52
+ showsHorizontalScrollIndicator={false}
53
+ style={styles.scroll}
54
+ >
55
+ {images.map((uri, index) => (
56
+ <GridImageItem
57
+ key={index}
58
+ styles={styles}
59
+ uri={uri}
60
+ index={index}
61
+ onRemove={() => onRemoveImage(index)}
62
+ />
63
+ ))}
64
+
65
+ {images.length < maxImages && (
66
+ <AddMoreCard
67
+ styles={styles}
68
+ addMoreText={translations.addMore}
69
+ onPress={onSelectImages}
70
+ />
71
+ )}
72
+ </ScrollView>
73
+ </View>
74
+ );
75
+ };
76
+
77
+ export type { ImageSelectionGridProps, ImageSelectionGridTranslations } from "./ImageSelectionGrid.types";
@@ -0,0 +1,18 @@
1
+ /**
2
+ * ImageSelectionGrid Type Definitions
3
+ */
4
+
5
+ export interface ImageSelectionGridTranslations {
6
+ selectedImages: string;
7
+ selectImages: string;
8
+ chooseUpTo: string;
9
+ addMore: string;
10
+ }
11
+
12
+ export interface ImageSelectionGridProps {
13
+ images: string[];
14
+ maxImages: number;
15
+ onSelectImages: () => void;
16
+ onRemoveImage: (index: number) => void;
17
+ translations: ImageSelectionGridTranslations;
18
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Music Mood Selector Component
3
+ * Generic component for music mood selection
4
+ */
5
+
6
+ import React from "react";
7
+ import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
8
+ import {
9
+ AtomicText,
10
+ AtomicIcon,
11
+ useAppDesignTokens,
12
+ } from "@umituz/react-native-design-system";
13
+ import type { MusicMood, MusicMoodId } from "../../domain/types";
14
+
15
+ export interface MusicMoodSelectorProps {
16
+ moods: MusicMood[];
17
+ selectedMood: MusicMoodId;
18
+ onMoodSelect: (moodId: MusicMoodId) => void;
19
+ label: string;
20
+ hasCustomAudio?: boolean;
21
+ customAudioLabel?: string;
22
+ }
23
+
24
+ export const MusicMoodSelector: React.FC<MusicMoodSelectorProps> = ({
25
+ moods,
26
+ selectedMood,
27
+ onMoodSelect,
28
+ label,
29
+ hasCustomAudio = false,
30
+ customAudioLabel,
31
+ }) => {
32
+ const tokens = useAppDesignTokens();
33
+
34
+ return (
35
+ <View style={componentStyles.section}>
36
+ <AtomicText
37
+ type="bodyMedium"
38
+ style={[componentStyles.label, { color: tokens.colors.textPrimary }]}
39
+ >
40
+ {label}
41
+ </AtomicText>
42
+ <ScrollView
43
+ horizontal
44
+ showsHorizontalScrollIndicator={false}
45
+ style={componentStyles.scroll}
46
+ >
47
+ {moods.map((mood) => {
48
+ const isSelected = selectedMood === mood.id;
49
+ return (
50
+ <TouchableOpacity
51
+ key={mood.id}
52
+ style={[
53
+ componentStyles.card,
54
+ {
55
+ backgroundColor: isSelected
56
+ ? tokens.colors.primary
57
+ : tokens.colors.surface,
58
+ borderColor: isSelected
59
+ ? tokens.colors.primary
60
+ : tokens.colors.borderLight,
61
+ },
62
+ ]}
63
+ onPress={() => onMoodSelect(mood.id)}
64
+ activeOpacity={0.7}
65
+ >
66
+ <View
67
+ style={[
68
+ componentStyles.iconContainer,
69
+ {
70
+ backgroundColor: isSelected
71
+ ? tokens.colors.modalOverlay
72
+ : tokens.colors.surface,
73
+ },
74
+ ]}
75
+ >
76
+ <AtomicIcon
77
+ name={mood.icon as never}
78
+ size="lg"
79
+ color={isSelected ? "onSurface" : "primary"}
80
+ />
81
+ </View>
82
+ <AtomicText
83
+ type="bodySmall"
84
+ style={[
85
+ componentStyles.moodName,
86
+ {
87
+ color: isSelected
88
+ ? tokens.colors.textInverse
89
+ : tokens.colors.textPrimary,
90
+ },
91
+ ]}
92
+ >
93
+ {mood.name}
94
+ </AtomicText>
95
+ <AtomicText
96
+ type="labelSmall"
97
+ style={[
98
+ componentStyles.moodDescription,
99
+ {
100
+ color: isSelected
101
+ ? tokens.colors.textInverse
102
+ : tokens.colors.textSecondary,
103
+ opacity: isSelected ? 0.9 : 0.7,
104
+ },
105
+ ]}
106
+ >
107
+ {mood.description}
108
+ </AtomicText>
109
+ </TouchableOpacity>
110
+ );
111
+ })}
112
+ </ScrollView>
113
+ {selectedMood === "custom" && hasCustomAudio && customAudioLabel && (
114
+ <View
115
+ style={[
116
+ componentStyles.customBadge,
117
+ { backgroundColor: tokens.colors.surfaceVariant },
118
+ ]}
119
+ >
120
+ <AtomicIcon name="checkmark-circle-outline" size="sm" color="primary" />
121
+ <AtomicText
122
+ type="labelSmall"
123
+ style={[componentStyles.customBadgeText, { color: tokens.colors.primary }]}
124
+ >
125
+ {customAudioLabel}
126
+ </AtomicText>
127
+ </View>
128
+ )}
129
+ </View>
130
+ );
131
+ };
132
+
133
+ const componentStyles = StyleSheet.create({
134
+ section: {
135
+ padding: 16,
136
+ marginBottom: 8,
137
+ },
138
+ label: {
139
+ fontWeight: "600",
140
+ marginBottom: 12,
141
+ },
142
+ scroll: {
143
+ marginHorizontal: -16,
144
+ paddingHorizontal: 16,
145
+ },
146
+ card: {
147
+ width: 140,
148
+ padding: 16,
149
+ borderRadius: 16,
150
+ borderWidth: 2,
151
+ marginRight: 12,
152
+ alignItems: "center",
153
+ },
154
+ iconContainer: {
155
+ width: 56,
156
+ height: 56,
157
+ borderRadius: 28,
158
+ alignItems: "center",
159
+ justifyContent: "center",
160
+ },
161
+ moodName: {
162
+ fontWeight: "600",
163
+ marginTop: 8,
164
+ textAlign: "center",
165
+ },
166
+ moodDescription: {
167
+ marginTop: 4,
168
+ textAlign: "center",
169
+ },
170
+ customBadge: {
171
+ flexDirection: "row",
172
+ alignItems: "center",
173
+ padding: 12,
174
+ borderRadius: 12,
175
+ marginTop: 12,
176
+ },
177
+ customBadgeText: {
178
+ marginLeft: 8,
179
+ flex: 1,
180
+ },
181
+ });
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Image-to-Video Components Index
3
+ */
4
+
5
+ // Animation Style Selector
6
+ export { AnimationStyleSelector as ImageToVideoAnimationStyleSelector } from "./AnimationStyleSelector";
7
+ export type { AnimationStyleSelectorProps as ImageToVideoAnimationStyleSelectorProps } from "./AnimationStyleSelector";
8
+
9
+ // Duration Selector
10
+ export { DurationSelector as ImageToVideoDurationSelector } from "./DurationSelector";
11
+ export type { DurationSelectorProps as ImageToVideoDurationSelectorProps } from "./DurationSelector";
12
+
13
+ // Music Mood Selector
14
+ export { MusicMoodSelector as ImageToVideoMusicMoodSelector } from "./MusicMoodSelector";
15
+ export type { MusicMoodSelectorProps as ImageToVideoMusicMoodSelectorProps } from "./MusicMoodSelector";
16
+
17
+ // Image Selection Grid
18
+ export { ImageSelectionGrid as ImageToVideoSelectionGrid } from "./ImageSelectionGrid";
19
+ export type {
20
+ ImageSelectionGridProps as ImageToVideoSelectionGridProps,
21
+ ImageSelectionGridTranslations as ImageToVideoSelectionGridTranslations,
22
+ } from "./ImageSelectionGrid";
23
+
24
+ // Hero Section - Using AIGenerationHero from common components
25
+ // export { HeroSection as ImageToVideoHeroSection } from "./HeroSection";
26
+ // export type { HeroSectionProps as ImageToVideoHeroSectionProps } from "./HeroSection";
27
+
28
+ // Action Components
29
+ export { GenerateButton as ImageToVideoGenerateButton } from "../../../../presentation/components/buttons";
30
+ export type { GenerateButtonProps as ImageToVideoGenerateButtonProps } from "../../../../presentation/components/buttons";
@@ -0,0 +1,27 @@
1
+ // Form State Hook
2
+ export { useFormState as useImageToVideoFormState } from "./useFormState";
3
+ export type {
4
+ UseFormStateOptions as UseImageToVideoFormStateOptions,
5
+ UseFormStateReturn as UseImageToVideoFormStateReturn,
6
+ } from "./useFormState";
7
+
8
+ // Generation Hook
9
+ export { useGeneration as useImageToVideoGeneration } from "./useGeneration";
10
+ export type {
11
+ UseGenerationOptions as UseImageToVideoGenerationOptions,
12
+ UseGenerationReturn as UseImageToVideoGenerationReturn,
13
+ } from "./useGeneration";
14
+
15
+ // Combined Form Hook
16
+ export { useImageToVideoForm } from "./useImageToVideoForm";
17
+ export type {
18
+ UseImageToVideoFormOptions,
19
+ UseImageToVideoFormReturn,
20
+ } from "./useImageToVideoForm";
21
+
22
+ // Provider-based Feature Hook
23
+ export { useImageToVideoFeature } from "./useImageToVideoFeature";
24
+ export type {
25
+ UseImageToVideoFeatureProps,
26
+ UseImageToVideoFeatureReturn,
27
+ } from "./useImageToVideoFeature";
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Form State Hook for Image-to-Video
3
+ * Manages form state with actions
4
+ */
5
+
6
+ import { useState, useCallback, useMemo } from "react";
7
+ import type {
8
+ ImageToVideoFormState,
9
+ ImageToVideoFormActions,
10
+ ImageToVideoFormDefaults,
11
+ AnimationStyleId,
12
+ MusicMoodId,
13
+ VideoDuration,
14
+ } from "../../domain/types";
15
+ import {
16
+ DEFAULT_ANIMATION_STYLE_ID,
17
+ DEFAULT_MUSIC_MOOD_ID,
18
+ DEFAULT_VIDEO_DURATION,
19
+ } from "../../domain/constants";
20
+
21
+ export interface UseFormStateOptions {
22
+ defaults?: ImageToVideoFormDefaults;
23
+ }
24
+
25
+ export interface UseFormStateReturn {
26
+ state: ImageToVideoFormState;
27
+ actions: ImageToVideoFormActions;
28
+ }
29
+
30
+ function createInitialState(defaults?: ImageToVideoFormDefaults): ImageToVideoFormState {
31
+ return {
32
+ selectedImages: [],
33
+ animationStyle: defaults?.animationStyle ?? DEFAULT_ANIMATION_STYLE_ID,
34
+ duration: defaults?.duration ?? DEFAULT_VIDEO_DURATION,
35
+ musicMood: defaults?.musicMood ?? DEFAULT_MUSIC_MOOD_ID,
36
+ customAudioUri: null,
37
+ motionPrompt: "",
38
+ };
39
+ }
40
+
41
+ export function useFormState(options?: UseFormStateOptions): UseFormStateReturn {
42
+ const { defaults } = options ?? {};
43
+
44
+ const [state, setState] = useState<ImageToVideoFormState>(() =>
45
+ createInitialState(defaults)
46
+ );
47
+
48
+ const setSelectedImages = useCallback((images: string[]) => {
49
+ setState((prev) => ({ ...prev, selectedImages: images }));
50
+ }, []);
51
+
52
+ const addImages = useCallback((images: string[]) => {
53
+ setState((prev) => ({
54
+ ...prev,
55
+ selectedImages: [...prev.selectedImages, ...images],
56
+ }));
57
+ }, []);
58
+
59
+ const removeImage = useCallback((index: number) => {
60
+ setState((prev) => ({
61
+ ...prev,
62
+ selectedImages: prev.selectedImages.filter((_, i) => i !== index),
63
+ }));
64
+ }, []);
65
+
66
+ const setAnimationStyle = useCallback((style: AnimationStyleId) => {
67
+ setState((prev) => ({ ...prev, animationStyle: style }));
68
+ }, []);
69
+
70
+ const setDuration = useCallback((duration: VideoDuration) => {
71
+ setState((prev) => ({ ...prev, duration }));
72
+ }, []);
73
+
74
+ const setMusicMood = useCallback((mood: MusicMoodId) => {
75
+ setState((prev) => ({ ...prev, musicMood: mood }));
76
+ }, []);
77
+
78
+ const setCustomAudioUri = useCallback((uri: string | null) => {
79
+ setState((prev) => ({ ...prev, customAudioUri: uri }));
80
+ }, []);
81
+
82
+ const setMotionPrompt = useCallback((prompt: string) => {
83
+ setState((prev) => ({ ...prev, motionPrompt: prompt }));
84
+ }, []);
85
+
86
+ const reset = useCallback(() => {
87
+ setState(createInitialState(defaults));
88
+ }, [defaults]);
89
+
90
+ const actions = useMemo<ImageToVideoFormActions>(
91
+ () => ({
92
+ setSelectedImages,
93
+ addImages,
94
+ removeImage,
95
+ setAnimationStyle,
96
+ setDuration,
97
+ setMusicMood,
98
+ setCustomAudioUri,
99
+ setMotionPrompt,
100
+ reset,
101
+ }),
102
+ [
103
+ setSelectedImages,
104
+ addImages,
105
+ removeImage,
106
+ setAnimationStyle,
107
+ setDuration,
108
+ setMusicMood,
109
+ setCustomAudioUri,
110
+ setMotionPrompt,
111
+ reset,
112
+ ]
113
+ );
114
+
115
+ return { state, actions };
116
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Generation Hook for Image-to-Video
3
+ * Manages generation state and execution
4
+ */
5
+
6
+ import { useState, useCallback, useMemo } from "react";
7
+ import type {
8
+ ImageToVideoFormState,
9
+ ImageToVideoGenerationState,
10
+ ImageToVideoCallbacks,
11
+ } from "../../domain/types";
12
+
13
+ export interface UseGenerationOptions {
14
+ formState: ImageToVideoFormState;
15
+ callbacks: ImageToVideoCallbacks;
16
+ }
17
+
18
+ export interface UseGenerationReturn {
19
+ generationState: ImageToVideoGenerationState;
20
+ handleGenerate: () => Promise<void>;
21
+ setProgress: (progress: number) => void;
22
+ setError: (error: string | null) => void;
23
+ isReady: boolean;
24
+ }
25
+
26
+ const INITIAL_GENERATION_STATE: ImageToVideoGenerationState = {
27
+ isGenerating: false,
28
+ progress: 0,
29
+ error: null,
30
+ };
31
+
32
+ export function useGeneration(options: UseGenerationOptions): UseGenerationReturn {
33
+ const { formState, callbacks } = options;
34
+
35
+ const [generationState, setGenerationState] = useState<ImageToVideoGenerationState>(
36
+ INITIAL_GENERATION_STATE
37
+ );
38
+
39
+ const setProgress = useCallback((progress: number) => {
40
+ setGenerationState((prev) => ({ ...prev, progress }));
41
+ }, []);
42
+
43
+ const setError = useCallback((error: string | null) => {
44
+ setGenerationState((prev) => ({ ...prev, error, isGenerating: false }));
45
+ }, []);
46
+
47
+ const handleGenerate = useCallback(async () => {
48
+ if (formState.selectedImages.length === 0) {
49
+ callbacks.onError?.("No images selected");
50
+ return;
51
+ }
52
+
53
+ setGenerationState({
54
+ isGenerating: true,
55
+ progress: 0,
56
+ error: null,
57
+ });
58
+
59
+ try {
60
+ await callbacks.onGenerate(formState);
61
+ setGenerationState((prev) => ({ ...prev, isGenerating: false, progress: 100 }));
62
+ } catch (error) {
63
+ const errorMessage = error instanceof Error ? error.message : String(error);
64
+ setGenerationState({
65
+ isGenerating: false,
66
+ progress: 0,
67
+ error: errorMessage,
68
+ });
69
+ callbacks.onError?.(errorMessage);
70
+ }
71
+ }, [formState, callbacks]);
72
+
73
+ const isReady = useMemo(
74
+ () => formState.selectedImages.length > 0 && !generationState.isGenerating,
75
+ [formState.selectedImages.length, generationState.isGenerating]
76
+ );
77
+
78
+ return {
79
+ generationState,
80
+ handleGenerate,
81
+ setProgress,
82
+ setError,
83
+ isReady,
84
+ };
85
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * useGenerationExecution Hook
3
+ * Handles the core generation execution logic for image-to-video
4
+ */
5
+
6
+ import { useCallback } from "react";
7
+ import { executeImageToVideo } from "../../infrastructure/services";
8
+ import type {
9
+ ImageToVideoFeatureConfig,
10
+ ImageToVideoFeatureCallbacks,
11
+ ImageToVideoResult,
12
+ ImageToVideoGenerateParams,
13
+ ImageToVideoFeatureState,
14
+ } from "../../domain/types";
15
+
16
+ declare const __DEV__: boolean;
17
+
18
+ interface UseGenerationExecutionParams {
19
+ userId: string;
20
+ config: ImageToVideoFeatureConfig;
21
+ callbacks?: ImageToVideoFeatureCallbacks;
22
+ setState: React.Dispatch<React.SetStateAction<ImageToVideoFeatureState>>;
23
+ }
24
+
25
+ export function useGenerationExecution({
26
+ userId,
27
+ config,
28
+ callbacks,
29
+ setState,
30
+ }: UseGenerationExecutionParams) {
31
+ return useCallback(
32
+ async (
33
+ imageUri: string,
34
+ motionPrompt: string,
35
+ options?: Omit<ImageToVideoGenerateParams, "imageUri" | "motionPrompt">,
36
+ ): Promise<ImageToVideoResult> => {
37
+ const creationId = `image-to-video_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
38
+
39
+ setState((prev) => ({
40
+ ...prev,
41
+ imageUri,
42
+ isProcessing: true,
43
+ progress: 0,
44
+ error: null,
45
+ }));
46
+
47
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
48
+ console.log("[ImageToVideoFeature] Starting generation, creationId:", creationId);
49
+ }
50
+
51
+ config.onProcessingStart?.();
52
+
53
+ if (callbacks?.onGenerationStart) {
54
+ callbacks.onGenerationStart({
55
+ creationId,
56
+ type: "image-to-video",
57
+ imageUri,
58
+ metadata: options as Record<string, unknown> | undefined,
59
+ }).catch((err) => {
60
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
61
+ console.warn("[ImageToVideoFeature] onGenerationStart failed:", err);
62
+ }
63
+ });
64
+ }
65
+
66
+ try {
67
+ const imageBase64 = await config.prepareImage(imageUri);
68
+
69
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
70
+ console.log("[ImageToVideoFeature] Image prepared, calling executeImageToVideo");
71
+ }
72
+
73
+ const result = await executeImageToVideo(
74
+ {
75
+ imageUri,
76
+ imageBase64,
77
+ userId,
78
+ motionPrompt: motionPrompt || undefined,
79
+ options,
80
+ },
81
+ {
82
+ model: config.model,
83
+ buildInput: config.buildInput,
84
+ extractResult: config.extractResult,
85
+ onProgress: (progress) => {
86
+ setState((prev) => ({ ...prev, progress }));
87
+ callbacks?.onProgress?.(progress);
88
+ },
89
+ },
90
+ );
91
+
92
+ if (result.success && result.videoUrl) {
93
+ setState((prev) => ({
94
+ ...prev,
95
+ videoUrl: result.videoUrl ?? null,
96
+ thumbnailUrl: result.thumbnailUrl ?? null,
97
+ isProcessing: false,
98
+ progress: 100,
99
+ }));
100
+
101
+ if (callbacks?.onCreditDeduct && config.creditCost) {
102
+ await callbacks.onCreditDeduct(config.creditCost);
103
+ }
104
+
105
+ if (callbacks?.onCreationSave) {
106
+ await callbacks.onCreationSave({
107
+ creationId,
108
+ type: "image-to-video",
109
+ videoUrl: result.videoUrl,
110
+ thumbnailUrl: result.thumbnailUrl,
111
+ imageUri,
112
+ metadata: options as Record<string, unknown> | undefined,
113
+ });
114
+ }
115
+
116
+ callbacks?.onGenerate?.(result);
117
+ } else {
118
+ const error = result.error || "Generation failed";
119
+ setState((prev) => ({ ...prev, isProcessing: false, error }));
120
+ config.onError?.(error);
121
+ callbacks?.onError?.(error);
122
+ }
123
+
124
+ config.onProcessingComplete?.(result);
125
+ return result;
126
+ } catch (err) {
127
+ const errorMessage = err instanceof Error ? err.message : String(err);
128
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
129
+ console.error("[ImageToVideoFeature] Generation error:", errorMessage);
130
+ }
131
+ setState((prev) => ({
132
+ ...prev,
133
+ isProcessing: false,
134
+ error: errorMessage,
135
+ }));
136
+ config.onError?.(errorMessage);
137
+ callbacks?.onError?.(errorMessage);
138
+ return { success: false, error: errorMessage };
139
+ }
140
+ },
141
+ [userId, config, callbacks],
142
+ );
143
+ }