@umituz/react-native-ai-generation-content 1.61.63 → 1.61.64

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 (114) hide show
  1. package/package.json +1 -1
  2. package/src/core/index.ts +1 -1
  3. package/src/domain/entities/index.ts +1 -1
  4. package/src/domain/interfaces/ai-provider.interface.ts +1 -1
  5. package/src/domain/interfaces/index.ts +1 -1
  6. package/src/domains/background/domain/entities/index.ts +1 -0
  7. package/src/domains/background/domain/interfaces/index.ts +1 -0
  8. package/src/{domain → domains/background/domain}/interfaces/provider-job-manager.interface.ts +1 -1
  9. package/src/domains/background/domain/types/background-generation.types.ts +28 -0
  10. package/src/domains/background/infrastructure/executors/backgroundJobExecutor.ts +105 -0
  11. package/src/{infrastructure → domains/background/infrastructure}/services/job-poller-factory.ts +1 -1
  12. package/src/{infrastructure → domains/background/infrastructure}/services/job-poller.service.ts +1 -1
  13. package/src/{infrastructure → domains/background/infrastructure}/services/job-poller.types.ts +2 -2
  14. package/src/{infrastructure → domains/background/infrastructure}/utils/polling-interval.util.ts +1 -1
  15. package/src/{infrastructure → domains/background/infrastructure}/utils/status-checker.util.ts +1 -1
  16. package/src/domains/background/presentation/hooks/use-background-generation.ts +97 -0
  17. package/src/domains/creations/presentation/components/PendingJobsSection.tsx +1 -1
  18. package/src/domains/image-to-video/presentation/hooks/imageToVideoStrategy.ts +77 -0
  19. package/src/domains/image-to-video/presentation/hooks/useImageToVideoFeature.ts +102 -0
  20. package/src/domains/scenarios/presentation/containers/CategoryNavigationContainer.tsx +4 -80
  21. package/src/{features → domains}/text-to-image/infrastructure/services/text-to-image-executor.ts +2 -82
  22. package/src/domains/text-to-image/infrastructure/utils/imageResultExtractor.ts +58 -0
  23. package/src/domains/text-to-video/presentation/hooks/textToVideoStrategy.ts +75 -0
  24. package/src/domains/text-to-video/presentation/hooks/useTextToVideoFeature.ts +120 -0
  25. package/src/exports/features.ts +12 -12
  26. package/src/presentation/components/PendingJobCard.tsx +1 -1
  27. package/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.ts +0 -186
  28. package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +0 -186
  29. package/src/presentation/hooks/use-background-generation.ts +0 -185
  30. /package/src/{domain → domains/background/domain}/entities/job.types.ts +0 -0
  31. /package/src/{infrastructure → domains/background/infrastructure}/utils/result-validator.util.ts +0 -0
  32. /package/src/{presentation → domains/background/presentation}/hooks/use-pending-jobs.ts +0 -0
  33. /package/src/{features → domains}/image-to-video/README.md +0 -0
  34. /package/src/{features → domains}/image-to-video/domain/constants/animation.constants.ts +0 -0
  35. /package/src/{features → domains}/image-to-video/domain/constants/duration.constants.ts +0 -0
  36. /package/src/{features → domains}/image-to-video/domain/constants/form.constants.ts +0 -0
  37. /package/src/{features → domains}/image-to-video/domain/constants/index.ts +0 -0
  38. /package/src/{features → domains}/image-to-video/domain/constants/music.constants.ts +0 -0
  39. /package/src/{features → domains}/image-to-video/domain/index.ts +0 -0
  40. /package/src/{features → domains}/image-to-video/domain/types/animation.types.ts +0 -0
  41. /package/src/{features → domains}/image-to-video/domain/types/config.types.ts +0 -0
  42. /package/src/{features → domains}/image-to-video/domain/types/duration.types.ts +0 -0
  43. /package/src/{features → domains}/image-to-video/domain/types/form.types.ts +0 -0
  44. /package/src/{features → domains}/image-to-video/domain/types/image-to-video.types.ts +0 -0
  45. /package/src/{features → domains}/image-to-video/domain/types/index.ts +0 -0
  46. /package/src/{features → domains}/image-to-video/domain/types/music.types.ts +0 -0
  47. /package/src/{features → domains}/image-to-video/index.ts +0 -0
  48. /package/src/{features → domains}/image-to-video/infrastructure/index.ts +0 -0
  49. /package/src/{features → domains}/image-to-video/infrastructure/services/image-to-video-executor.ts +0 -0
  50. /package/src/{features → domains}/image-to-video/infrastructure/services/index.ts +0 -0
  51. /package/src/{features → domains}/image-to-video/presentation/components/AddMoreCard.tsx +0 -0
  52. /package/src/{features → domains}/image-to-video/presentation/components/AnimationStyleSelector.tsx +0 -0
  53. /package/src/{features → domains}/image-to-video/presentation/components/DurationSelector.tsx +0 -0
  54. /package/src/{features → domains}/image-to-video/presentation/components/EmptyGridState.tsx +0 -0
  55. /package/src/{features → domains}/image-to-video/presentation/components/GridImageItem.tsx +0 -0
  56. /package/src/{features → domains}/image-to-video/presentation/components/ImageSelectionGrid.styles.ts +0 -0
  57. /package/src/{features → domains}/image-to-video/presentation/components/ImageSelectionGrid.tsx +0 -0
  58. /package/src/{features → domains}/image-to-video/presentation/components/ImageSelectionGrid.types.ts +0 -0
  59. /package/src/{features → domains}/image-to-video/presentation/components/MusicMoodSelector.tsx +0 -0
  60. /package/src/{features → domains}/image-to-video/presentation/components/index.ts +0 -0
  61. /package/src/{features → domains}/image-to-video/presentation/hooks/image-to-video-feature.types.ts +0 -0
  62. /package/src/{features → domains}/image-to-video/presentation/hooks/index.ts +0 -0
  63. /package/src/{features → domains}/image-to-video/presentation/hooks/useFormState.ts +0 -0
  64. /package/src/{features → domains}/image-to-video/presentation/hooks/useGeneration.ts +0 -0
  65. /package/src/{features → domains}/image-to-video/presentation/hooks/useImageToVideoForm.ts +0 -0
  66. /package/src/{features → domains}/image-to-video/presentation/index.ts +0 -0
  67. /package/src/{features → domains}/image-to-video/presentation/screens/ImageToVideoWizardFlow.tsx +0 -0
  68. /package/src/{features → domains}/shared/index.ts +0 -0
  69. /package/src/{features → domains}/shared/presentation/components/AutoSkipPreview.tsx +0 -0
  70. /package/src/{features → domains}/shared/presentation/components/index.ts +0 -0
  71. /package/src/{features → domains}/shared/presentation/utils/index.ts +0 -0
  72. /package/src/{features → domains}/shared/presentation/utils/wizard-flow.utils.ts +0 -0
  73. /package/src/{features → domains}/text-to-image/README.md +0 -0
  74. /package/src/{features → domains}/text-to-image/domain/constants/index.ts +0 -0
  75. /package/src/{features → domains}/text-to-image/domain/constants/options.constants.ts +0 -0
  76. /package/src/{features → domains}/text-to-image/domain/constants/styles.constants.ts +0 -0
  77. /package/src/{features → domains}/text-to-image/domain/index.ts +0 -0
  78. /package/src/{features → domains}/text-to-image/domain/types/config.types.ts +0 -0
  79. /package/src/{features → domains}/text-to-image/domain/types/form.types.ts +0 -0
  80. /package/src/{features → domains}/text-to-image/domain/types/index.ts +0 -0
  81. /package/src/{features → domains}/text-to-image/domain/types/text-to-image.types.ts +0 -0
  82. /package/src/{features → domains}/text-to-image/index.ts +0 -0
  83. /package/src/{features → domains}/text-to-image/infrastructure/index.ts +0 -0
  84. /package/src/{features → domains}/text-to-image/infrastructure/services/index.ts +0 -0
  85. /package/src/{features → domains}/text-to-image/presentation/components/index.ts +0 -0
  86. /package/src/{features → domains}/text-to-image/presentation/hooks/index.ts +0 -0
  87. /package/src/{features → domains}/text-to-image/presentation/hooks/useFormState.ts +0 -0
  88. /package/src/{features → domains}/text-to-image/presentation/hooks/useGeneration.ts +0 -0
  89. /package/src/{features → domains}/text-to-image/presentation/hooks/useTextToImageForm.ts +0 -0
  90. /package/src/{features → domains}/text-to-image/presentation/index.ts +0 -0
  91. /package/src/{features → domains}/text-to-image/presentation/screens/TextToImageWizardFlow.tsx +0 -0
  92. /package/src/{features → domains}/text-to-image/presentation/screens/TextToImageWizardFlow.types.ts +0 -0
  93. /package/src/{features → domains}/text-to-video/README.md +0 -0
  94. /package/src/{features → domains}/text-to-video/domain/index.ts +0 -0
  95. /package/src/{features → domains}/text-to-video/domain/types/callback.types.ts +0 -0
  96. /package/src/{features → domains}/text-to-video/domain/types/component.types.ts +0 -0
  97. /package/src/{features → domains}/text-to-video/domain/types/config.types.ts +0 -0
  98. /package/src/{features → domains}/text-to-video/domain/types/index.ts +0 -0
  99. /package/src/{features → domains}/text-to-video/domain/types/request.types.ts +0 -0
  100. /package/src/{features → domains}/text-to-video/domain/types/state.types.ts +0 -0
  101. /package/src/{features → domains}/text-to-video/index.ts +0 -0
  102. /package/src/{features → domains}/text-to-video/infrastructure/index.ts +0 -0
  103. /package/src/{features → domains}/text-to-video/infrastructure/services/index.ts +0 -0
  104. /package/src/{features → domains}/text-to-video/infrastructure/services/text-to-video-executor.ts +0 -0
  105. /package/src/{features → domains}/text-to-video/presentation/components/FrameSelector.tsx +0 -0
  106. /package/src/{features → domains}/text-to-video/presentation/components/GenerationTabs.tsx +0 -0
  107. /package/src/{features → domains}/text-to-video/presentation/components/HeroSection.tsx +0 -0
  108. /package/src/{features → domains}/text-to-video/presentation/components/HintCarousel.tsx +0 -0
  109. /package/src/{features → domains}/text-to-video/presentation/components/OptionsPanel.tsx +0 -0
  110. /package/src/{features → domains}/text-to-video/presentation/components/index.ts +0 -0
  111. /package/src/{features → domains}/text-to-video/presentation/hooks/index.ts +0 -0
  112. /package/src/{features → domains}/text-to-video/presentation/hooks/useTextToVideoForm.ts +0 -0
  113. /package/src/{features → domains}/text-to-video/presentation/index.ts +0 -0
  114. /package/src/{features → domains}/text-to-video/presentation/screens/TextToVideoWizardFlow.tsx +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.61.63",
3
+ "version": "1.61.64",
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",
package/src/core/index.ts CHANGED
@@ -57,7 +57,7 @@ export type {
57
57
  // Segregated provider sub-interfaces
58
58
  export type { IAIProviderLifecycle } from "../domain/interfaces/provider-lifecycle.interface";
59
59
  export type { IAIProviderCapabilities } from "../domain/interfaces/provider-capabilities.interface";
60
- export type { IAIProviderJobManager } from "../domain/interfaces/provider-job-manager.interface";
60
+ export type { IAIProviderJobManager } from "../domains/background/domain/interfaces/provider-job-manager.interface";
61
61
  export type { IAIProviderExecutor } from "../domain/interfaces/provider-executor.interface";
62
62
  export type { IAIProviderImageFeatures } from "../domain/interfaces/provider-image-features.interface";
63
63
  export type { IAIProviderVideoFeatures } from "../domain/interfaces/provider-video-features.interface";
@@ -6,6 +6,6 @@
6
6
  export * from "./error.types";
7
7
  export * from "./generation.types";
8
8
  export * from "./polling.types";
9
- export * from "./job.types";
9
+ export * from "../../domains/background/domain/entities/job.types";
10
10
  export * from "./processing-modes.types";
11
11
  export * from "./flow-config.types";
@@ -155,7 +155,7 @@ export interface VideoFeatureInputData {
155
155
 
156
156
  import type { IAIProviderLifecycle } from "./provider-lifecycle.interface";
157
157
  import type { IAIProviderCapabilities } from "./provider-capabilities.interface";
158
- import type { IAIProviderJobManager } from "./provider-job-manager.interface";
158
+ import type { IAIProviderJobManager } from "../../domains/background/domain/interfaces/provider-job-manager.interface";
159
159
  import type { IAIProviderExecutor } from "./provider-executor.interface";
160
160
  import type { IAIProviderImageFeatures } from "./provider-image-features.interface";
161
161
  import type { IAIProviderVideoFeatures } from "./provider-video-features.interface";
@@ -9,7 +9,7 @@ export * from "./app-services.interface";
9
9
  // Interface Segregation - Split provider interfaces
10
10
  export type { IAIProviderLifecycle } from "./provider-lifecycle.interface";
11
11
  export type { IAIProviderCapabilities } from "./provider-capabilities.interface";
12
- export type { IAIProviderJobManager } from "./provider-job-manager.interface";
12
+ export type { IAIProviderJobManager } from "../../domains/background/domain/interfaces/provider-job-manager.interface";
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";
@@ -0,0 +1 @@
1
+ export * from "./job.types";
@@ -0,0 +1 @@
1
+ export * from "./provider-job-manager.interface";
@@ -3,7 +3,7 @@
3
3
  * Single Responsibility: Async job submission, status checking, and result retrieval
4
4
  */
5
5
 
6
- import type { JobSubmission, JobStatus } from "./ai-provider.interface";
6
+ import type { JobSubmission, JobStatus } from "../../../../domain/interfaces/ai-provider.interface";
7
7
 
8
8
  export interface IAIProviderJobManager {
9
9
  /**
@@ -0,0 +1,28 @@
1
+ import type {
2
+ BackgroundJob,
3
+ BackgroundQueueConfig,
4
+ JobExecutorConfig,
5
+ } from "../entities/job.types";
6
+ import type { executeDirectGeneration } from "../../infrastructure/executors/backgroundJobExecutor";
7
+
8
+ export type { DirectExecutionResult } from "../../infrastructure/executors/backgroundJobExecutor";
9
+
10
+ export interface UseBackgroundGenerationOptions<TInput, TResult>
11
+ extends Partial<BackgroundQueueConfig> {
12
+ readonly executor: JobExecutorConfig<TInput, TResult>;
13
+ readonly onJobComplete?: (job: BackgroundJob<TInput, TResult>) => void;
14
+ readonly onJobError?: (job: BackgroundJob<TInput, TResult>) => void;
15
+ readonly onAllComplete?: () => void;
16
+ readonly onProgress?: (progress: number) => void;
17
+ }
18
+
19
+ export interface UseBackgroundGenerationReturn<TInput, TResult> {
20
+ readonly startJob: (input: TInput, type: string) => Promise<string>;
21
+ readonly executeDirectly: (input: TInput) => ReturnType<typeof executeDirectGeneration<TInput, TResult>>;
22
+ readonly cancelJob: (id: string) => void;
23
+ readonly pendingJobs: BackgroundJob<TInput, TResult>[];
24
+ readonly activeJobCount: number;
25
+ readonly hasActiveJobs: boolean;
26
+ readonly isProcessing: boolean;
27
+ readonly progress: number;
28
+ }
@@ -0,0 +1,105 @@
1
+ import type { BackgroundJob, JobExecutorConfig } from "../../domain/entities/job.types";
2
+
3
+ export interface DirectExecutionResult<TResult> {
4
+ readonly success: boolean;
5
+ readonly result?: TResult;
6
+ readonly error?: string;
7
+ }
8
+
9
+ interface DirectExecutionParams<TInput, TResult> {
10
+ input: TInput;
11
+ executor: JobExecutorConfig<TInput, TResult>;
12
+ onProgress?: (progress: number) => void;
13
+ setProgress: (progress: number) => void;
14
+ setIsProcessing: (isProcessing: boolean) => void;
15
+ }
16
+
17
+ export const executeDirectGeneration = async <TInput, TResult>(
18
+ params: DirectExecutionParams<TInput, TResult>,
19
+ ): Promise<DirectExecutionResult<TResult>> => {
20
+ const { input, executor, onProgress, setProgress, setIsProcessing } = params;
21
+
22
+ setIsProcessing(true);
23
+ setProgress(0);
24
+
25
+ try {
26
+ const result = await executor.execute(input, (p) => {
27
+ setProgress(p);
28
+ onProgress?.(p);
29
+ });
30
+
31
+ setProgress(100);
32
+ return { success: true, result };
33
+ } catch (error) {
34
+ const errorMsg = error instanceof Error ? error.message : String(error);
35
+ return { success: false, error: errorMsg };
36
+ } finally {
37
+ setIsProcessing(false);
38
+ }
39
+ };
40
+
41
+ interface QueuedExecutionParams<TInput, TResult> {
42
+ jobId: string;
43
+ input: TInput;
44
+ executor: JobExecutorConfig<TInput, TResult>;
45
+ updateJob: (params: { id: string; updates: Partial<BackgroundJob<TInput, TResult>> }) => void;
46
+ removeJob: (id: string) => void;
47
+ getJob: (id: string) => BackgroundJob<TInput, TResult> | undefined;
48
+ activeJobsRef: React.MutableRefObject<Set<string>>;
49
+ onJobComplete?: (job: BackgroundJob<TInput, TResult>) => void;
50
+ onJobError?: (job: BackgroundJob<TInput, TResult>) => void;
51
+ onAllComplete?: () => void;
52
+ }
53
+
54
+ export const executeQueuedJob = async <TInput, TResult>(
55
+ params: QueuedExecutionParams<TInput, TResult>,
56
+ ): Promise<void> => {
57
+ const {
58
+ jobId,
59
+ input,
60
+ executor,
61
+ updateJob,
62
+ removeJob,
63
+ getJob,
64
+ activeJobsRef,
65
+ onJobComplete,
66
+ onJobError,
67
+ onAllComplete,
68
+ } = params;
69
+
70
+ try {
71
+ updateJob({ id: jobId, updates: { status: "processing", progress: 10 } });
72
+
73
+ const result = await executor.execute(input, (p) => {
74
+ updateJob({ id: jobId, updates: { progress: p } });
75
+ });
76
+
77
+ updateJob({
78
+ id: jobId,
79
+ updates: { status: "completed", progress: 100, result, completedAt: new Date() },
80
+ });
81
+
82
+ const completedJob = getJob(jobId);
83
+ if (completedJob) {
84
+ await executor.onComplete?.(completedJob);
85
+ onJobComplete?.(completedJob);
86
+ }
87
+
88
+ removeJob(jobId);
89
+ } catch (error) {
90
+ const errorMsg = error instanceof Error ? error.message : String(error);
91
+
92
+ updateJob({ id: jobId, updates: { status: "failed", error: errorMsg, progress: 0 } });
93
+
94
+ const failedJob = getJob(jobId);
95
+ if (failedJob) {
96
+ await executor.onError?.(failedJob, error instanceof Error ? error : new Error(errorMsg));
97
+ onJobError?.(failedJob);
98
+ }
99
+ } finally {
100
+ activeJobsRef.current.delete(jobId);
101
+ if (activeJobsRef.current.size === 0) {
102
+ onAllComplete?.();
103
+ }
104
+ }
105
+ };
@@ -3,7 +3,7 @@
3
3
  * Creates pre-configured job poller instances
4
4
  */
5
5
 
6
- import type { PollingConfig } from "../../domain/entities";
6
+ import type { PollingConfig } from "../../../../domain/entities/polling.types";
7
7
  import type { PollJobOptions } from "./job-poller.types";
8
8
  import { pollJob } from "./job-poller.service";
9
9
 
@@ -4,7 +4,7 @@
4
4
  * Reports only real status - no fake progress
5
5
  */
6
6
 
7
- import { DEFAULT_POLLING_CONFIG } from "../../domain/entities";
7
+ import { DEFAULT_POLLING_CONFIG } from "../../../../domain/entities/polling.types";
8
8
  import { calculatePollingInterval } from "../utils/polling-interval.util";
9
9
  import { checkStatusForErrors, isJobComplete } from "../utils/status-checker.util";
10
10
  import { validateResult } from "../utils/result-validator.util";
@@ -2,8 +2,8 @@
2
2
  * Job Poller Type Definitions
3
3
  */
4
4
 
5
- import type { IAIProvider, JobStatus } from "../../domain/interfaces";
6
- import type { PollingConfig } from "../../domain/entities";
5
+ import type { IAIProvider, JobStatus } from "../../../../domain/interfaces/ai-provider.interface";
6
+ import type { PollingConfig } from "../../../../domain/entities/polling.types";
7
7
 
8
8
  export interface PollJobOptions {
9
9
  provider: IAIProvider;
@@ -6,7 +6,7 @@
6
6
  import {
7
7
  DEFAULT_POLLING_CONFIG,
8
8
  type PollingConfig,
9
- } from "../../domain/entities";
9
+ } from "../../../../domain/entities/polling.types";
10
10
 
11
11
  export interface IntervalOptions {
12
12
  attempt: number;
@@ -3,7 +3,7 @@
3
3
  * Checks job status responses for errors
4
4
  */
5
5
 
6
- import type { JobStatus, AILogEntry } from "../../domain/interfaces";
6
+ import type { JobStatus, AILogEntry } from "../../../../domain/interfaces/ai-provider.interface";
7
7
 
8
8
  export interface StatusCheckResult {
9
9
  status: string;
@@ -0,0 +1,97 @@
1
+ import { useCallback, useRef, useState } from "react";
2
+ import { usePendingJobs } from "./use-pending-jobs";
3
+ import { executeDirectGeneration, executeQueuedJob } from "../../infrastructure/executors/backgroundJobExecutor";
4
+ import { DEFAULT_QUEUE_CONFIG } from "../../domain/entities/job.types";
5
+ import type {
6
+ UseBackgroundGenerationOptions,
7
+ UseBackgroundGenerationReturn,
8
+ } from "../../domain/types/background-generation.types";
9
+
10
+ export type { DirectExecutionResult, UseBackgroundGenerationOptions, UseBackgroundGenerationReturn } from "../../domain/types/background-generation.types";
11
+
12
+ export function useBackgroundGeneration<TInput = unknown, TResult = unknown>(
13
+ options: UseBackgroundGenerationOptions<TInput, TResult>,
14
+ ): UseBackgroundGenerationReturn<TInput, TResult> {
15
+ const config = { ...DEFAULT_QUEUE_CONFIG, ...options };
16
+ const activeJobsRef = useRef<Set<string>>(new Set());
17
+ const jobInputsRef = useRef<Map<string, { input: TInput; type: string }>>(
18
+ new Map(),
19
+ );
20
+
21
+ const [isProcessing, setIsProcessing] = useState(false);
22
+ const [progress, setProgress] = useState(0);
23
+
24
+ const { jobs, addJobAsync, updateJob, removeJob, getJob } = usePendingJobs<
25
+ TInput,
26
+ TResult
27
+ >({
28
+ queryKey: config.queryKey,
29
+ });
30
+
31
+ const { executor, onProgress, onJobComplete, onJobError, onAllComplete } = options;
32
+
33
+ const executeDirectly = useCallback(
34
+ (input: TInput) =>
35
+ executeDirectGeneration({ input, executor, onProgress, setProgress, setIsProcessing }),
36
+ [executor, onProgress],
37
+ );
38
+
39
+ const executeJob = useCallback(
40
+ (jobId: string, input: TInput) =>
41
+ executeQueuedJob({
42
+ jobId,
43
+ input,
44
+ executor,
45
+ updateJob,
46
+ removeJob,
47
+ getJob,
48
+ activeJobsRef,
49
+ onJobComplete,
50
+ onJobError,
51
+ onAllComplete,
52
+ }),
53
+ [executor, onJobComplete, onJobError, onAllComplete, updateJob, removeJob, getJob],
54
+ );
55
+
56
+ const startJob = useCallback(
57
+ async (input: TInput, type: string): Promise<string> => {
58
+ const jobId = `job-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
59
+
60
+ jobInputsRef.current.set(jobId, { input, type });
61
+
62
+ await addJobAsync({
63
+ id: jobId,
64
+ input,
65
+ type,
66
+ status: "queued",
67
+ progress: 0,
68
+ });
69
+
70
+ activeJobsRef.current.add(jobId);
71
+ void executeJob(jobId, input);
72
+
73
+ return jobId;
74
+ },
75
+ [addJobAsync, executeJob],
76
+ );
77
+
78
+ const cancelJob = useCallback(
79
+ (id: string) => {
80
+ activeJobsRef.current.delete(id);
81
+ jobInputsRef.current.delete(id);
82
+ removeJob(id);
83
+ },
84
+ [removeJob],
85
+ );
86
+
87
+ return {
88
+ startJob,
89
+ executeDirectly,
90
+ cancelJob,
91
+ pendingJobs: jobs,
92
+ activeJobCount: activeJobsRef.current.size,
93
+ hasActiveJobs: activeJobsRef.current.size > 0,
94
+ isProcessing,
95
+ progress,
96
+ };
97
+ }
@@ -6,7 +6,7 @@
6
6
  import React from "react";
7
7
  import { View, StyleSheet } from "react-native";
8
8
  import { AtomicText, useAppDesignTokens } from "@umituz/react-native-design-system";
9
- import type { BackgroundJob } from "../../../../domain/entities/job.types";
9
+ import type { BackgroundJob } from "../../../background/domain/entities/job.types";
10
10
  import { PendingJobCard } from "../../../../presentation/components/PendingJobCard";
11
11
 
12
12
  export interface PendingJobsSectionProps {
@@ -0,0 +1,77 @@
1
+ import { executeImageToVideo } from "../../infrastructure/services";
2
+ import type { GenerationStrategy } from "../../../../presentation/hooks/generation";
3
+ import type {
4
+ ImageToVideoConfig,
5
+ ImageToVideoCallbacks,
6
+ ImageToVideoResult,
7
+ ImageToVideoOptions,
8
+ ImageToVideoInputBuilder,
9
+ ImageToVideoResultExtractor,
10
+ } from "../../domain/types";
11
+
12
+ interface VideoGenerationInput {
13
+ imageUrl: string;
14
+ prompt: string;
15
+ options?: ImageToVideoOptions;
16
+ creationId: string;
17
+ }
18
+
19
+ interface CreateStrategyParams {
20
+ config: ImageToVideoConfig;
21
+ callbacks: ImageToVideoCallbacks;
22
+ buildInput: ImageToVideoInputBuilder;
23
+ extractResult?: ImageToVideoResultExtractor;
24
+ userId: string;
25
+ currentPrompt: string;
26
+ creationIdRef: React.MutableRefObject<string>;
27
+ updateState: (videoUrl: string | null, thumbnailUrl: string | null) => void;
28
+ }
29
+
30
+ export const createImageToVideoStrategy = (
31
+ params: CreateStrategyParams,
32
+ ): GenerationStrategy<VideoGenerationInput, ImageToVideoResult> => {
33
+ const { config, callbacks, buildInput, extractResult, userId, currentPrompt, creationIdRef, updateState } = params;
34
+
35
+ return {
36
+ execute: async (input) => {
37
+ creationIdRef.current = input.creationId;
38
+
39
+ callbacks.onGenerationStart?.({
40
+ creationId: input.creationId,
41
+ type: "image-to-video",
42
+ imageUrl: input.imageUrl,
43
+ prompt: input.prompt,
44
+ metadata: input.options as Record<string, unknown> | undefined,
45
+ }).catch(() => {});
46
+
47
+ const result = await executeImageToVideo(
48
+ { imageUrl: input.imageUrl, prompt: input.prompt, userId, options: input.options },
49
+ { model: config.model, buildInput, extractResult },
50
+ );
51
+
52
+ if (!result.success || !result.videoUrl) {
53
+ throw new Error(result.error || "Generation failed");
54
+ }
55
+
56
+ updateState(result.videoUrl ?? null, result.thumbnailUrl ?? null);
57
+
58
+ return {
59
+ success: true,
60
+ videoUrl: result.videoUrl,
61
+ thumbnailUrl: result.thumbnailUrl,
62
+ };
63
+ },
64
+ getCreditCost: () => config.creditCost,
65
+ save: async (result) => {
66
+ if (result.success && result.videoUrl && creationIdRef.current) {
67
+ await callbacks.onCreationSave?.({
68
+ creationId: creationIdRef.current,
69
+ type: "image-to-video",
70
+ videoUrl: result.videoUrl,
71
+ thumbnailUrl: result.thumbnailUrl,
72
+ prompt: currentPrompt,
73
+ });
74
+ }
75
+ },
76
+ };
77
+ };
@@ -0,0 +1,102 @@
1
+ import { useState, useCallback, useMemo, useRef } from "react";
2
+ import { useGenerationOrchestrator } from "../../../../presentation/hooks/generation";
3
+ import { createImageToVideoStrategy } from "./imageToVideoStrategy";
4
+ import type {
5
+ UseImageToVideoFeatureProps,
6
+ UseImageToVideoFeatureReturn,
7
+ INITIAL_STATE,
8
+ DEFAULT_ALERT_MESSAGES,
9
+ } from "./image-to-video-feature.types";
10
+
11
+ export type {
12
+ UseImageToVideoFeatureProps,
13
+ UseImageToVideoFeatureReturn,
14
+ } from "./image-to-video-feature.types";
15
+
16
+ export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseImageToVideoFeatureReturn {
17
+ const { config, callbacks, userId } = props;
18
+ const [state, setState] = useState(INITIAL_STATE);
19
+ const creationIdRef = useRef("");
20
+
21
+ const updateState = useCallback((videoUrl: string | null, thumbnailUrl: string | null) => {
22
+ setState((prev) => ({ ...prev, videoUrl, thumbnailUrl }));
23
+ }, []);
24
+
25
+ const strategy = useMemo(
26
+ () =>
27
+ createImageToVideoStrategy({
28
+ config,
29
+ callbacks,
30
+ buildInput: config.buildInput,
31
+ extractResult: config.extractResult,
32
+ userId,
33
+ currentPrompt: state.motionPrompt || "",
34
+ creationIdRef,
35
+ updateState,
36
+ }),
37
+ [config, callbacks, userId, state.motionPrompt, updateState],
38
+ );
39
+
40
+ const orchestrator = useGenerationOrchestrator(strategy, {
41
+ userId,
42
+ alertMessages: DEFAULT_ALERT_MESSAGES,
43
+ onCreditsExhausted: () => callbacks.onShowPaywall?.(config.creditCost ?? 0),
44
+ onSuccess: (result) => {
45
+ config.onProcessingComplete?.();
46
+ callbacks.onGenerate?.(result);
47
+ },
48
+ onError: (err) => {
49
+ config.onProcessingError?.(err.message);
50
+ callbacks.onError?.(err.message);
51
+ },
52
+ });
53
+
54
+ const setImageUri = useCallback((imageUri: string) => {
55
+ setState((prev) => ({ ...prev, imageUri, error: null }));
56
+ }, []);
57
+
58
+ const setMotionPrompt = useCallback((motionPrompt: string) => {
59
+ setState((prev) => ({ ...prev, motionPrompt, error: null }));
60
+ }, []);
61
+
62
+ const generate = useCallback(
63
+ async (params?: any) => {
64
+ const imageUri = params?.imageUri || state.imageUri;
65
+ if (!imageUri) {
66
+ const error = "Image is required";
67
+ setState((prev) => ({ ...prev, error }));
68
+ return { success: false, error };
69
+ }
70
+
71
+ setState((prev) => ({ ...prev, isProcessing: true, error: null }));
72
+
73
+ try {
74
+ const result = await orchestrator.generate({
75
+ imageUrl: imageUri,
76
+ prompt: state.motionPrompt || "",
77
+ options: params,
78
+ creationId: `image-to-video-${Date.now()}`,
79
+ });
80
+ setState((prev) => ({ ...prev, isProcessing: false }));
81
+ return result;
82
+ } catch (error) {
83
+ const message = error instanceof Error ? error.message : "Generation failed";
84
+ setState((prev) => ({ ...prev, isProcessing: false, error: message }));
85
+ return { success: false, error: message };
86
+ }
87
+ },
88
+ [state.imageUri, state.motionPrompt, orchestrator],
89
+ );
90
+
91
+ const reset = useCallback(() => setState(INITIAL_STATE), []);
92
+
93
+ return {
94
+ state,
95
+ setImageUri,
96
+ setMotionPrompt,
97
+ generate,
98
+ reset,
99
+ isReady: !orchestrator.isGenerating && !state.isProcessing,
100
+ canGenerate: !orchestrator.isGenerating && !state.isProcessing && !!state.imageUri,
101
+ };
102
+ }