@umituz/react-native-ai-generation-content 1.17.143 → 1.17.144

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.17.143",
3
+ "version": "1.17.144",
4
4
  "description": "Provider-agnostic AI generation orchestration for React Native",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",
@@ -6,6 +6,6 @@ export { useCreations } from "./useCreations";
6
6
  export { useDeleteCreation } from "./useDeleteCreation";
7
7
  export { useCreationsFilter } from "./useCreationsFilter";
8
8
  export { useAdvancedFilter } from "./useAdvancedFilter";
9
- export { useStatusFilter } from "./useStatusFilter";
10
- export { useMediaFilter } from "./useMediaFilter";
9
+ export { useFilter, useStatusFilter, useMediaFilter } from "./useFilter";
10
+ export type { UseFilterProps, UseFilterReturn } from "./useFilter";
11
11
  export { useGalleryFilters } from "./useGalleryFilters";
@@ -1,19 +1,20 @@
1
1
  /**
2
- * useMediaFilter Hook
3
- * Handles media type filtering (image, video, voice)
4
- * SOLID: Single Responsibility - Only handles media filter state
2
+ * useFilter Hook
3
+ * Generic filter state management for any filter type
4
+ * SOLID: Single Responsibility - Only handles filter state
5
+ * DRY: Replaces duplicate useMediaFilter and useStatusFilter
5
6
  */
6
7
 
7
8
  import { useState, useCallback, useMemo } from "react";
8
9
  import type { FilterOption } from "../../domain/types/creation-filter";
9
10
 
10
- interface UseMediaFilterProps {
11
+ export interface UseFilterProps {
11
12
  readonly options: FilterOption[];
12
13
  readonly t: (key: string) => string;
13
14
  readonly defaultId?: string;
14
15
  }
15
16
 
16
- interface UseMediaFilterReturn {
17
+ export interface UseFilterReturn {
17
18
  readonly selectedId: string;
18
19
  readonly filterOptions: FilterOption[];
19
20
  readonly hasActiveFilter: boolean;
@@ -21,11 +22,11 @@ interface UseMediaFilterReturn {
21
22
  readonly clearFilter: () => void;
22
23
  }
23
24
 
24
- export function useMediaFilter({
25
+ export function useFilter({
25
26
  options,
26
27
  t,
27
28
  defaultId = "all"
28
- }: UseMediaFilterProps): UseMediaFilterReturn {
29
+ }: UseFilterProps): UseFilterReturn {
29
30
  const [selectedId, setSelectedId] = useState(defaultId);
30
31
 
31
32
  const filterOptions = useMemo(() =>
@@ -52,3 +53,7 @@ export function useMediaFilter({
52
53
  clearFilter
53
54
  };
54
55
  }
56
+
57
+ // Backward compatibility aliases
58
+ export const useMediaFilter = useFilter;
59
+ export const useStatusFilter = useFilter;
@@ -7,8 +7,7 @@
7
7
  import { useState, useCallback } from "react";
8
8
  import type { Creation } from "../../domain/entities/Creation";
9
9
  import type { FilterOption } from "../../domain/types/creation-filter";
10
- import { useStatusFilter } from "./useStatusFilter";
11
- import { useMediaFilter } from "./useMediaFilter";
10
+ import { useFilter } from "./useFilter";
12
11
  import { useCreationsFilter } from "./useCreationsFilter";
13
12
 
14
13
  interface UseGalleryFiltersProps {
@@ -24,8 +23,8 @@ interface UseGalleryFiltersReturn {
24
23
  readonly activeFiltersCount: number;
25
24
  readonly statusFilterVisible: boolean;
26
25
  readonly mediaFilterVisible: boolean;
27
- readonly statusFilter: ReturnType<typeof useStatusFilter>;
28
- readonly mediaFilter: ReturnType<typeof useMediaFilter>;
26
+ readonly statusFilter: ReturnType<typeof useFilter>;
27
+ readonly mediaFilter: ReturnType<typeof useFilter>;
29
28
  readonly openStatusFilter: () => void;
30
29
  readonly closeStatusFilter: () => void;
31
30
  readonly openMediaFilter: () => void;
@@ -42,8 +41,8 @@ export function useGalleryFilters({
42
41
  const [statusFilterVisible, setStatusFilterVisible] = useState(false);
43
42
  const [mediaFilterVisible, setMediaFilterVisible] = useState(false);
44
43
 
45
- const statusFilter = useStatusFilter({ options: statusOptions, t });
46
- const mediaFilter = useMediaFilter({ options: mediaOptions, t });
44
+ const statusFilter = useFilter({ options: statusOptions, t });
45
+ const mediaFilter = useFilter({ options: mediaOptions, t });
47
46
 
48
47
  const { filtered, isFiltered, activeFiltersCount } = useCreationsFilter({
49
48
  creations,
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * useAIHugFeature Hook
3
- * Manages AI hug video generation state and actions
3
+ * Thin wrapper around useDualImageVideoFeature for ai-hug feature
4
+ * DRY: Uses shared base hook for common logic
4
5
  */
5
6
 
6
- import { useState, useCallback } from "react";
7
- import { executeVideoFeature } from "../../../../infrastructure/services";
7
+ import { useDualImageVideoFeature } from "../../../shared/dual-image-video";
8
8
  import type {
9
- AIHugFeatureState,
10
9
  AIHugFeatureConfig,
10
+ AIHugFeatureState,
11
11
  } from "../../domain/types";
12
12
 
13
13
  export interface UseAIHugFeatureProps {
@@ -25,113 +25,9 @@ export interface UseAIHugFeatureReturn extends AIHugFeatureState {
25
25
  reset: () => void;
26
26
  }
27
27
 
28
- const initialState: AIHugFeatureState = {
29
- sourceImageUri: null,
30
- targetImageUri: null,
31
- processedVideoUrl: null,
32
- isProcessing: false,
33
- progress: 0,
34
- error: null,
35
- };
36
-
37
- export function useAIHugFeature(
38
- props: UseAIHugFeatureProps,
39
- ): UseAIHugFeatureReturn {
40
- const { config, onSelectSourceImage, onSelectTargetImage, onSaveVideo } = props;
41
- const [state, setState] = useState<AIHugFeatureState>(initialState);
42
-
43
- const selectSourceImage = useCallback(async () => {
44
- try {
45
- const uri = await onSelectSourceImage();
46
- if (uri) {
47
- setState((prev) => ({ ...prev, sourceImageUri: uri, error: null }));
48
- config.onSourceImageSelect?.(uri);
49
- }
50
- } catch (error) {
51
- const message = error instanceof Error ? error.message : String(error);
52
- setState((prev) => ({ ...prev, error: message }));
53
- }
54
- }, [onSelectSourceImage, config]);
55
-
56
- const selectTargetImage = useCallback(async () => {
57
- try {
58
- const uri = await onSelectTargetImage();
59
- if (uri) {
60
- setState((prev) => ({ ...prev, targetImageUri: uri, error: null }));
61
- config.onTargetImageSelect?.(uri);
62
- }
63
- } catch (error) {
64
- const message = error instanceof Error ? error.message : String(error);
65
- setState((prev) => ({ ...prev, error: message }));
66
- }
67
- }, [onSelectTargetImage, config]);
68
-
69
- const handleProgress = useCallback((progress: number) => {
70
- setState((prev) => ({ ...prev, progress }));
71
- }, []);
72
-
73
- const process = useCallback(async () => {
74
- if (!state.sourceImageUri || !state.targetImageUri) return;
75
-
76
- setState((prev) => ({
77
- ...prev,
78
- isProcessing: true,
79
- progress: 0,
80
- error: null,
81
- }));
82
-
83
- config.onProcessingStart?.();
84
-
85
- const sourceImageBase64 = await config.prepareImage(state.sourceImageUri);
86
- const targetImageBase64 = await config.prepareImage(state.targetImageUri);
87
-
88
- const result = await executeVideoFeature(
89
- "ai-hug",
90
- { sourceImageBase64, targetImageBase64 },
91
- { extractResult: config.extractResult, onProgress: handleProgress },
92
- );
93
-
94
- if (result.success && result.videoUrl) {
95
- setState((prev) => ({
96
- ...prev,
97
- isProcessing: false,
98
- processedVideoUrl: result.videoUrl!,
99
- progress: 100,
100
- }));
101
- config.onProcessingComplete?.({ success: true, videoUrl: result.videoUrl });
102
- } else {
103
- const errorMessage = result.error || "Processing failed";
104
- setState((prev) => ({
105
- ...prev,
106
- isProcessing: false,
107
- error: errorMessage,
108
- progress: 0,
109
- }));
110
- config.onError?.(errorMessage);
111
- }
112
- }, [state.sourceImageUri, state.targetImageUri, config, handleProgress]);
113
-
114
- const save = useCallback(async () => {
115
- if (!state.processedVideoUrl) return;
116
-
117
- try {
118
- await onSaveVideo(state.processedVideoUrl);
119
- } catch (error) {
120
- const message = error instanceof Error ? error.message : String(error);
121
- setState((prev) => ({ ...prev, error: message }));
122
- }
123
- }, [state.processedVideoUrl, onSaveVideo]);
124
-
125
- const reset = useCallback(() => {
126
- setState(initialState);
127
- }, []);
128
-
129
- return {
130
- ...state,
131
- selectSourceImage,
132
- selectTargetImage,
133
- process,
134
- save,
135
- reset,
136
- };
28
+ export function useAIHugFeature(props: UseAIHugFeatureProps): UseAIHugFeatureReturn {
29
+ return useDualImageVideoFeature({
30
+ featureType: "ai-hug",
31
+ ...props,
32
+ });
137
33
  }
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * useAIKissFeature Hook
3
- * Manages AI kiss video generation state and actions
3
+ * Thin wrapper around useDualImageVideoFeature for ai-kiss feature
4
+ * DRY: Uses shared base hook for common logic
4
5
  */
5
6
 
6
- import { useState, useCallback } from "react";
7
- import { executeVideoFeature } from "../../../../infrastructure/services";
7
+ import { useDualImageVideoFeature } from "../../../shared/dual-image-video";
8
8
  import type {
9
- AIKissFeatureState,
10
9
  AIKissFeatureConfig,
10
+ AIKissFeatureState,
11
11
  } from "../../domain/types";
12
12
 
13
13
  export interface UseAIKissFeatureProps {
@@ -25,113 +25,9 @@ export interface UseAIKissFeatureReturn extends AIKissFeatureState {
25
25
  reset: () => void;
26
26
  }
27
27
 
28
- const initialState: AIKissFeatureState = {
29
- sourceImageUri: null,
30
- targetImageUri: null,
31
- processedVideoUrl: null,
32
- isProcessing: false,
33
- progress: 0,
34
- error: null,
35
- };
36
-
37
- export function useAIKissFeature(
38
- props: UseAIKissFeatureProps,
39
- ): UseAIKissFeatureReturn {
40
- const { config, onSelectSourceImage, onSelectTargetImage, onSaveVideo } = props;
41
- const [state, setState] = useState<AIKissFeatureState>(initialState);
42
-
43
- const selectSourceImage = useCallback(async () => {
44
- try {
45
- const uri = await onSelectSourceImage();
46
- if (uri) {
47
- setState((prev) => ({ ...prev, sourceImageUri: uri, error: null }));
48
- config.onSourceImageSelect?.(uri);
49
- }
50
- } catch (error) {
51
- const message = error instanceof Error ? error.message : String(error);
52
- setState((prev) => ({ ...prev, error: message }));
53
- }
54
- }, [onSelectSourceImage, config]);
55
-
56
- const selectTargetImage = useCallback(async () => {
57
- try {
58
- const uri = await onSelectTargetImage();
59
- if (uri) {
60
- setState((prev) => ({ ...prev, targetImageUri: uri, error: null }));
61
- config.onTargetImageSelect?.(uri);
62
- }
63
- } catch (error) {
64
- const message = error instanceof Error ? error.message : String(error);
65
- setState((prev) => ({ ...prev, error: message }));
66
- }
67
- }, [onSelectTargetImage, config]);
68
-
69
- const handleProgress = useCallback((progress: number) => {
70
- setState((prev) => ({ ...prev, progress }));
71
- }, []);
72
-
73
- const process = useCallback(async () => {
74
- if (!state.sourceImageUri || !state.targetImageUri) return;
75
-
76
- setState((prev) => ({
77
- ...prev,
78
- isProcessing: true,
79
- progress: 0,
80
- error: null,
81
- }));
82
-
83
- config.onProcessingStart?.();
84
-
85
- const sourceImageBase64 = await config.prepareImage(state.sourceImageUri);
86
- const targetImageBase64 = await config.prepareImage(state.targetImageUri);
87
-
88
- const result = await executeVideoFeature(
89
- "ai-kiss",
90
- { sourceImageBase64, targetImageBase64 },
91
- { extractResult: config.extractResult, onProgress: handleProgress },
92
- );
93
-
94
- if (result.success && result.videoUrl) {
95
- setState((prev) => ({
96
- ...prev,
97
- isProcessing: false,
98
- processedVideoUrl: result.videoUrl!,
99
- progress: 100,
100
- }));
101
- config.onProcessingComplete?.({ success: true, videoUrl: result.videoUrl });
102
- } else {
103
- const errorMessage = result.error || "Processing failed";
104
- setState((prev) => ({
105
- ...prev,
106
- isProcessing: false,
107
- error: errorMessage,
108
- progress: 0,
109
- }));
110
- config.onError?.(errorMessage);
111
- }
112
- }, [state.sourceImageUri, state.targetImageUri, config, handleProgress]);
113
-
114
- const save = useCallback(async () => {
115
- if (!state.processedVideoUrl) return;
116
-
117
- try {
118
- await onSaveVideo(state.processedVideoUrl);
119
- } catch (error) {
120
- const message = error instanceof Error ? error.message : String(error);
121
- setState((prev) => ({ ...prev, error: message }));
122
- }
123
- }, [state.processedVideoUrl, onSaveVideo]);
124
-
125
- const reset = useCallback(() => {
126
- setState(initialState);
127
- }, []);
128
-
129
- return {
130
- ...state,
131
- selectSourceImage,
132
- selectTargetImage,
133
- process,
134
- save,
135
- reset,
136
- };
28
+ export function useAIKissFeature(props: UseAIKissFeatureProps): UseAIKissFeatureReturn {
29
+ return useDualImageVideoFeature({
30
+ featureType: "ai-kiss",
31
+ ...props,
32
+ });
137
33
  }
@@ -1,10 +1,11 @@
1
1
  /**
2
2
  * Image-to-Video Executor
3
3
  * Provider-agnostic image-to-video execution using active AI provider
4
- * Matches text-to-video pattern for consistency
4
+ * Uses progress mapper for consistent progress reporting
5
5
  */
6
6
 
7
7
  import { providerRegistry } from "../../../../infrastructure/services";
8
+ import { getProgressFromJobStatus } from "../../../../infrastructure/utils";
8
9
  import type {
9
10
  ImageToVideoRequest,
10
11
  ImageToVideoResult,
@@ -103,14 +104,9 @@ export async function executeImageToVideo(
103
104
  // eslint-disable-next-line no-console
104
105
  console.log("[ImageToVideoExecutor] Queue status:", status.status, "position:", status.queuePosition);
105
106
  }
106
- // Map queue status to progress
107
- if (status.status === "IN_QUEUE") {
108
- onProgress?.(10);
109
- } else if (status.status === "IN_PROGRESS") {
110
- onProgress?.(50);
111
- } else if (status.status === "COMPLETED") {
112
- onProgress?.(90);
113
- }
107
+ // Map provider status to progress using centralized mapper
108
+ const progress = getProgressFromJobStatus(status.status);
109
+ onProgress?.(progress);
114
110
  },
115
111
  timeoutMs: 300000, // 5 minutes timeout for video generation
116
112
  });
@@ -63,27 +63,32 @@ export function useImageToVideoForm(
63
63
 
64
64
  const handleSelectImages = useCallback(async () => {
65
65
  if (__DEV__) {
66
+ // eslint-disable-next-line no-console
66
67
  console.log("[useImageToVideoForm] handleSelectImages called");
67
68
  }
68
69
  if (callbacks.onSelectImages) {
69
70
  try {
70
71
  const images = await callbacks.onSelectImages();
71
72
  if (__DEV__) {
73
+ // eslint-disable-next-line no-console
72
74
  console.log("[useImageToVideoForm] Images selected:", images.length);
73
75
  }
74
76
  if (images.length > 0) {
75
77
  actions.addImages(images);
76
78
  if (__DEV__) {
79
+ // eslint-disable-next-line no-console
77
80
  console.log("[useImageToVideoForm] Images added to state");
78
81
  }
79
82
  }
80
83
  } catch (error) {
81
84
  if (__DEV__) {
85
+ // eslint-disable-next-line no-console
82
86
  console.error("[useImageToVideoForm] Error selecting images:", error);
83
87
  }
84
88
  }
85
89
  } else {
86
90
  if (__DEV__) {
91
+ // eslint-disable-next-line no-console
87
92
  console.warn("[useImageToVideoForm] No onSelectImages callback provided");
88
93
  }
89
94
  }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Dual Image Video Feature Types
3
+ * Shared types for video features that take two images (ai-hug, ai-kiss, etc.)
4
+ * DRY: Consolidates common types from ai-hug and ai-kiss features
5
+ */
6
+
7
+ import type { VideoFeatureType } from "../../../../../domain/interfaces/ai-provider.interface";
8
+
9
+ export interface DualImageVideoFeatureState {
10
+ sourceImageUri: string | null;
11
+ targetImageUri: string | null;
12
+ processedVideoUrl: string | null;
13
+ isProcessing: boolean;
14
+ progress: number;
15
+ error: string | null;
16
+ }
17
+
18
+ export interface DualImageVideoResult {
19
+ success: boolean;
20
+ videoUrl?: string;
21
+ error?: string;
22
+ requestId?: string;
23
+ }
24
+
25
+ export type DualImageVideoResultExtractor = (result: unknown) => string | undefined;
26
+
27
+ export interface DualImageVideoFeatureConfig {
28
+ creditCost?: number;
29
+ extractResult?: DualImageVideoResultExtractor;
30
+ prepareImage: (imageUri: string) => Promise<string>;
31
+ onSourceImageSelect?: (uri: string) => void;
32
+ onTargetImageSelect?: (uri: string) => void;
33
+ onProcessingStart?: () => void;
34
+ onProcessingComplete?: (result: DualImageVideoResult) => void;
35
+ onError?: (error: string) => void;
36
+ }
37
+
38
+ export interface DualImageVideoTranslations {
39
+ sourceUploadTitle: string;
40
+ sourceUploadSubtitle: string;
41
+ targetUploadTitle: string;
42
+ targetUploadSubtitle: string;
43
+ uploadChange: string;
44
+ uploadAnalyzing: string;
45
+ description: string;
46
+ processingText: string;
47
+ processButtonText: string;
48
+ successText: string;
49
+ saveButtonText: string;
50
+ tryAnotherText: string;
51
+ }
52
+
53
+ export interface UseDualImageVideoFeatureProps {
54
+ featureType: VideoFeatureType;
55
+ config: DualImageVideoFeatureConfig;
56
+ onSelectSourceImage: () => Promise<string | null>;
57
+ onSelectTargetImage: () => Promise<string | null>;
58
+ onSaveVideo: (videoUrl: string) => Promise<void>;
59
+ }
60
+
61
+ export interface UseDualImageVideoFeatureReturn extends DualImageVideoFeatureState {
62
+ selectSourceImage: () => Promise<void>;
63
+ selectTargetImage: () => Promise<void>;
64
+ process: () => Promise<void>;
65
+ save: () => Promise<void>;
66
+ reset: () => void;
67
+ }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Dual Image Video Domain Types
3
+ */
4
+
5
+ export type {
6
+ DualImageVideoFeatureState,
7
+ DualImageVideoResult,
8
+ DualImageVideoResultExtractor,
9
+ DualImageVideoFeatureConfig,
10
+ DualImageVideoTranslations,
11
+ UseDualImageVideoFeatureProps,
12
+ UseDualImageVideoFeatureReturn,
13
+ } from "./dual-image-video.types";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Dual Image Video Feature (Shared)
3
+ * Base infrastructure for video features using two source images
4
+ */
5
+
6
+ export type {
7
+ DualImageVideoFeatureState,
8
+ DualImageVideoResult,
9
+ DualImageVideoResultExtractor,
10
+ DualImageVideoFeatureConfig,
11
+ DualImageVideoTranslations,
12
+ UseDualImageVideoFeatureProps,
13
+ UseDualImageVideoFeatureReturn,
14
+ } from "./domain/types";
15
+
16
+ export { useDualImageVideoFeature } from "./presentation/hooks";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Dual Image Video Presentation Hooks
3
+ */
4
+
5
+ export { useDualImageVideoFeature } from "./useDualImageVideoFeature";
@@ -0,0 +1,124 @@
1
+ /**
2
+ * useDualImageVideoFeature Hook
3
+ * Base hook for video features that take two images (ai-hug, ai-kiss, etc.)
4
+ * DRY: Consolidates common logic from useAIHugFeature and useAIKissFeature
5
+ */
6
+
7
+ import { useState, useCallback } from "react";
8
+ import { executeVideoFeature } from "../../../../../infrastructure/services";
9
+ import type {
10
+ DualImageVideoFeatureState,
11
+ UseDualImageVideoFeatureProps,
12
+ UseDualImageVideoFeatureReturn,
13
+ } from "../../domain/types/dual-image-video.types";
14
+
15
+ const initialState: DualImageVideoFeatureState = {
16
+ sourceImageUri: null,
17
+ targetImageUri: null,
18
+ processedVideoUrl: null,
19
+ isProcessing: false,
20
+ progress: 0,
21
+ error: null,
22
+ };
23
+
24
+ export function useDualImageVideoFeature(
25
+ props: UseDualImageVideoFeatureProps,
26
+ ): UseDualImageVideoFeatureReturn {
27
+ const { featureType, config, onSelectSourceImage, onSelectTargetImage, onSaveVideo } = props;
28
+ const [state, setState] = useState<DualImageVideoFeatureState>(initialState);
29
+
30
+ const selectSourceImage = useCallback(async () => {
31
+ try {
32
+ const uri = await onSelectSourceImage();
33
+ if (uri) {
34
+ setState((prev) => ({ ...prev, sourceImageUri: uri, error: null }));
35
+ config.onSourceImageSelect?.(uri);
36
+ }
37
+ } catch (error) {
38
+ const message = error instanceof Error ? error.message : String(error);
39
+ setState((prev) => ({ ...prev, error: message }));
40
+ }
41
+ }, [onSelectSourceImage, config]);
42
+
43
+ const selectTargetImage = useCallback(async () => {
44
+ try {
45
+ const uri = await onSelectTargetImage();
46
+ if (uri) {
47
+ setState((prev) => ({ ...prev, targetImageUri: uri, error: null }));
48
+ config.onTargetImageSelect?.(uri);
49
+ }
50
+ } catch (error) {
51
+ const message = error instanceof Error ? error.message : String(error);
52
+ setState((prev) => ({ ...prev, error: message }));
53
+ }
54
+ }, [onSelectTargetImage, config]);
55
+
56
+ const handleProgress = useCallback((progress: number) => {
57
+ setState((prev) => ({ ...prev, progress }));
58
+ }, []);
59
+
60
+ const process = useCallback(async () => {
61
+ if (!state.sourceImageUri || !state.targetImageUri) return;
62
+
63
+ setState((prev) => ({
64
+ ...prev,
65
+ isProcessing: true,
66
+ progress: 0,
67
+ error: null,
68
+ }));
69
+
70
+ config.onProcessingStart?.();
71
+
72
+ const sourceImageBase64 = await config.prepareImage(state.sourceImageUri);
73
+ const targetImageBase64 = await config.prepareImage(state.targetImageUri);
74
+
75
+ const result = await executeVideoFeature(
76
+ featureType,
77
+ { sourceImageBase64, targetImageBase64 },
78
+ { extractResult: config.extractResult, onProgress: handleProgress },
79
+ );
80
+
81
+ if (result.success && result.videoUrl) {
82
+ setState((prev) => ({
83
+ ...prev,
84
+ isProcessing: false,
85
+ processedVideoUrl: result.videoUrl!,
86
+ progress: 100,
87
+ }));
88
+ config.onProcessingComplete?.({ success: true, videoUrl: result.videoUrl });
89
+ } else {
90
+ const errorMessage = result.error || "Processing failed";
91
+ setState((prev) => ({
92
+ ...prev,
93
+ isProcessing: false,
94
+ error: errorMessage,
95
+ progress: 0,
96
+ }));
97
+ config.onError?.(errorMessage);
98
+ }
99
+ }, [state.sourceImageUri, state.targetImageUri, featureType, config, handleProgress]);
100
+
101
+ const save = useCallback(async () => {
102
+ if (!state.processedVideoUrl) return;
103
+
104
+ try {
105
+ await onSaveVideo(state.processedVideoUrl);
106
+ } catch (error) {
107
+ const message = error instanceof Error ? error.message : String(error);
108
+ setState((prev) => ({ ...prev, error: message }));
109
+ }
110
+ }, [state.processedVideoUrl, onSaveVideo]);
111
+
112
+ const reset = useCallback(() => {
113
+ setState(initialState);
114
+ }, []);
115
+
116
+ return {
117
+ ...state,
118
+ selectSourceImage,
119
+ selectTargetImage,
120
+ process,
121
+ save,
122
+ reset,
123
+ };
124
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Shared Features
3
+ * Common infrastructure for multiple feature types
4
+ */
5
+
6
+ export * from "./dual-image-video";
@@ -1,6 +1,7 @@
1
1
  /**
2
2
  * Progress Calculator Utility
3
3
  * Calculates progress based on generation stage
4
+ * Maps provider status to generation progress
4
5
  */
5
6
 
6
7
  import type { GenerationStatus } from "../../domain/entities";
@@ -8,6 +9,7 @@ import {
8
9
  DEFAULT_PROGRESS_STAGES,
9
10
  type ProgressStageConfig,
10
11
  } from "../../domain/entities";
12
+ import type { AIJobStatusType } from "../../domain/interfaces/ai-provider.interface";
11
13
 
12
14
  export interface ProgressOptions {
13
15
  status: GenerationStatus;
@@ -95,3 +97,32 @@ export function createProgressTracker(stages?: ProgressStageConfig[]) {
95
97
  },
96
98
  };
97
99
  }
100
+
101
+ /**
102
+ * Maps provider job status to generation status
103
+ * Provider: IN_QUEUE, IN_PROGRESS, COMPLETED, FAILED
104
+ * Generation: idle, preparing, submitting, generating, polling, finalizing, completed, failed
105
+ */
106
+ export function mapJobStatusToGenerationStatus(
107
+ jobStatus: AIJobStatusType
108
+ ): GenerationStatus {
109
+ const statusMap: Record<AIJobStatusType, GenerationStatus> = {
110
+ IN_QUEUE: "submitting",
111
+ IN_PROGRESS: "generating",
112
+ COMPLETED: "completed",
113
+ FAILED: "failed",
114
+ };
115
+ return statusMap[jobStatus] ?? "generating";
116
+ }
117
+
118
+ /**
119
+ * Calculate progress from provider job status
120
+ * Uses default progress stages for consistent progress reporting
121
+ */
122
+ export function getProgressFromJobStatus(
123
+ jobStatus: AIJobStatusType,
124
+ stages: ProgressStageConfig[] = DEFAULT_PROGRESS_STAGES
125
+ ): number {
126
+ const generationStatus = mapJobStatusToGenerationStatus(jobStatus);
127
+ return getProgressForStatus({ status: generationStatus, stages });
128
+ }
@@ -1,54 +0,0 @@
1
- /**
2
- * useStatusFilter Hook
3
- * Handles status filtering (completed, pending, processing, failed)
4
- * SOLID: Single Responsibility - Only handles status filter state
5
- */
6
-
7
- import { useState, useCallback, useMemo } from "react";
8
- import type { FilterOption } from "../../domain/types/creation-filter";
9
-
10
- interface UseStatusFilterProps {
11
- readonly options: FilterOption[];
12
- readonly t: (key: string) => string;
13
- readonly defaultId?: string;
14
- }
15
-
16
- interface UseStatusFilterReturn {
17
- readonly selectedId: string;
18
- readonly filterOptions: FilterOption[];
19
- readonly hasActiveFilter: boolean;
20
- readonly selectFilter: (id: string) => void;
21
- readonly clearFilter: () => void;
22
- }
23
-
24
- export function useStatusFilter({
25
- options,
26
- t,
27
- defaultId = "all"
28
- }: UseStatusFilterProps): UseStatusFilterReturn {
29
- const [selectedId, setSelectedId] = useState(defaultId);
30
-
31
- const filterOptions = useMemo(() =>
32
- options.map(opt => ({
33
- ...opt,
34
- label: opt.labelKey ? t(opt.labelKey) : opt.label
35
- })),
36
- [options, t]
37
- );
38
-
39
- const selectFilter = useCallback((id: string) => {
40
- setSelectedId(id);
41
- }, []);
42
-
43
- const clearFilter = useCallback(() => {
44
- setSelectedId(defaultId);
45
- }, [defaultId]);
46
-
47
- return {
48
- selectedId,
49
- filterOptions,
50
- hasActiveFilter: selectedId !== defaultId,
51
- selectFilter,
52
- clearFilter
53
- };
54
- }