@umituz/react-native-ai-generation-content 1.61.25 → 1.61.27

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 (54) hide show
  1. package/package.json +1 -1
  2. package/src/core/types/error.types.ts +3 -30
  3. package/src/core/types/provider.types.ts +35 -188
  4. package/src/core/types/result.types.ts +12 -96
  5. package/src/domain/entities/flow-config.types.ts +2 -2
  6. package/src/domain/entities/generation.types.ts +1 -0
  7. package/src/domains/creations/presentation/hooks/useProcessingJobsPoller.ts +16 -4
  8. package/src/domains/generation/domain/generation.types.ts +4 -7
  9. package/src/domains/generation/index.ts +1 -1
  10. package/src/domains/generation/infrastructure/flow/useFlowStore.ts +2 -1
  11. package/src/domains/generation/presentation/useAIGeneration.hook.ts +3 -15
  12. package/src/domains/generation/wizard/presentation/hooks/useGenerationPhase.ts +2 -3
  13. package/src/domains/generation/wizard/presentation/hooks/usePhotoBlockingGeneration.ts +1 -1
  14. package/src/domains/generation/wizard/presentation/hooks/useVideoQueueGeneration.ts +4 -0
  15. package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +4 -2
  16. package/src/exports/infrastructure.ts +1 -1
  17. package/src/features/image-to-video/infrastructure/services/image-to-video-executor.ts +10 -37
  18. package/src/features/image-to-video/presentation/components/DurationSelector.tsx +1 -1
  19. package/src/features/image-to-video/presentation/hooks/image-to-video-feature.types.ts +2 -12
  20. package/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.ts +24 -13
  21. package/src/features/text-to-video/infrastructure/services/text-to-video-executor.ts +5 -37
  22. package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +26 -26
  23. package/src/infrastructure/orchestration/GenerationOrchestrator.ts +17 -14
  24. package/src/infrastructure/orchestration/orchestrator.types.ts +2 -5
  25. package/src/infrastructure/providers/generation-config.provider.tsx +4 -4
  26. package/src/infrastructure/services/generation-orchestrator.service.ts +7 -6
  27. package/src/infrastructure/services/job-poller.service.ts +2 -4
  28. package/src/infrastructure/services/multi-image-generation.executor.ts +6 -11
  29. package/src/infrastructure/utils/README.md +7 -7
  30. package/src/infrastructure/utils/base64.util.ts +15 -0
  31. package/src/infrastructure/utils/error-classifier.util.ts +27 -10
  32. package/src/infrastructure/utils/error-message-extractor.util.ts +3 -3
  33. package/src/infrastructure/utils/error-patterns.constants.ts +21 -8
  34. package/src/infrastructure/utils/id-generator.util.ts +11 -0
  35. package/src/infrastructure/utils/index.ts +3 -0
  36. package/src/infrastructure/utils/polling-interval.util.ts +1 -14
  37. package/src/infrastructure/utils/provider-validator.util.ts +2 -2
  38. package/src/infrastructure/utils/video-result-extractor.util.ts +38 -0
  39. package/src/presentation/components/AIGenerationForm.tsx +3 -3
  40. package/src/presentation/components/selectors/AspectRatioSelector.tsx +3 -3
  41. package/src/presentation/components/selectors/DurationSelector.tsx +3 -3
  42. package/src/presentation/components/selectors/GridSelector.tsx +5 -5
  43. package/src/presentation/components/selectors/StyleSelector.tsx +3 -3
  44. package/src/presentation/constants/alert-messages.ts +14 -0
  45. package/src/presentation/hooks/generation/errors.ts +5 -1
  46. package/src/presentation/hooks/generation/useImageGeneration.ts +6 -2
  47. package/src/presentation/hooks/generation/useVideoGeneration.ts +6 -2
  48. package/src/presentation/hooks/use-background-generation.ts +8 -10
  49. package/src/presentation/hooks/use-generation.ts +8 -1
  50. package/src/presentation/hooks/useGenerationFlow.ts +18 -23
  51. package/src/presentation/layouts/DualImageFeatureLayout.tsx +0 -1
  52. package/src/presentation/layouts/DualImageVideoFeatureLayout.tsx +1 -1
  53. package/src/presentation/layouts/SingleImageFeatureLayout.tsx +1 -1
  54. package/src/presentation/layouts/SingleImageWithPromptFeatureLayout.tsx +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.61.25",
3
+ "version": "1.61.27",
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",
@@ -1,36 +1,9 @@
1
1
  /**
2
2
  * AI Generation Error Types
3
- * Provider-agnostic error classification
3
+ * Re-exports from canonical domain source
4
4
  *
5
5
  * @module @umituz/react-native-ai-generation-content/core
6
6
  */
7
7
 
8
- export enum AIErrorType {
9
- NETWORK = "NETWORK",
10
- RATE_LIMIT = "RATE_LIMIT",
11
- AUTHENTICATION = "AUTHENTICATION",
12
- VALIDATION = "VALIDATION",
13
- CONTENT_POLICY = "CONTENT_POLICY",
14
- SERVER = "SERVER",
15
- TIMEOUT = "TIMEOUT",
16
- UNKNOWN = "UNKNOWN",
17
- }
18
-
19
- export interface AIErrorInfo {
20
- type: AIErrorType;
21
- messageKey: string;
22
- retryable: boolean;
23
- originalError?: unknown;
24
- statusCode?: number;
25
- }
26
-
27
- export interface AIErrorMessages {
28
- [AIErrorType.NETWORK]: string;
29
- [AIErrorType.RATE_LIMIT]: string;
30
- [AIErrorType.AUTHENTICATION]: string;
31
- [AIErrorType.VALIDATION]: string;
32
- [AIErrorType.CONTENT_POLICY]: string;
33
- [AIErrorType.SERVER]: string;
34
- [AIErrorType.TIMEOUT]: string;
35
- [AIErrorType.UNKNOWN]: string;
36
- }
8
+ export { AIErrorType } from "../../domain/entities/error.types";
9
+ export type { AIErrorInfo, AIErrorMessages } from "../../domain/entities/error.types";
@@ -1,192 +1,39 @@
1
1
  /**
2
2
  * AI Provider Types - Core interfaces for AI generation providers
3
+ * Re-exports from canonical domain sources
4
+ *
5
+ * @module @umituz/react-native-ai-generation-content/core
3
6
  */
4
7
 
5
- // Feature Types
6
- export type ImageFeatureType =
7
- | "upscale"
8
- | "photo-restore"
9
- | "face-swap"
10
- | "anime-selfie"
11
- | "remove-background"
12
- | "remove-object"
13
- | "hd-touch-up"
14
- | "replace-background";
15
-
16
- /**
17
- * Feature types for video generation (output: video)
18
- */
19
- export type VideoFeatureType = "image-to-video" | "text-to-video";
20
-
21
- // =============================================================================
22
- // Provider Configuration
23
- // =============================================================================
24
-
25
- export interface AIProviderConfig {
26
- apiKey: string;
27
- maxRetries?: number;
28
- baseDelay?: number;
29
- maxDelay?: number;
30
- defaultTimeoutMs?: number;
31
- textModel?: string;
32
- textToImageModel?: string;
33
- imageEditModel?: string;
34
- videoGenerationModel?: string;
35
- videoFeatureModels?: Partial<Record<VideoFeatureType, string>>;
36
- imageFeatureModels?: Partial<Record<ImageFeatureType, string>>;
37
- }
38
-
39
- // =============================================================================
40
- // Status Types
41
- // =============================================================================
42
-
43
- export type AIJobStatusType =
44
- | "IN_QUEUE"
45
- | "IN_PROGRESS"
46
- | "COMPLETED"
47
- | "FAILED";
48
-
49
- export interface AILogEntry {
50
- message: string;
51
- level: "info" | "warn" | "error";
52
- timestamp?: string;
53
- }
54
-
55
- export interface JobSubmission {
56
- requestId: string;
57
- statusUrl?: string;
58
- responseUrl?: string;
59
- }
60
-
61
- export interface JobStatus {
62
- status: AIJobStatusType;
63
- logs?: AILogEntry[];
64
- queuePosition?: number;
65
- eta?: number;
66
- }
67
-
68
- // =============================================================================
69
- // Progress & Options
70
- // =============================================================================
71
-
72
- export interface ProviderProgressInfo {
73
- progress: number;
74
- status?: AIJobStatusType;
75
- message?: string;
76
- estimatedTimeRemaining?: number;
77
- }
78
-
79
- export interface SubscribeOptions<T = unknown> {
80
- timeoutMs?: number;
81
- onQueueUpdate?: (status: JobStatus) => void;
82
- onProgress?: (progress: ProviderProgressInfo) => void;
83
- onResult?: (result: T) => void;
84
- }
85
-
86
- export interface RunOptions {
87
- onProgress?: (progress: ProviderProgressInfo) => void;
88
- }
89
-
90
- // =============================================================================
91
- // Capabilities
92
- // =============================================================================
93
-
94
- export interface ProviderCapabilities {
95
- imageFeatures: readonly ImageFeatureType[];
96
- videoFeatures: readonly VideoFeatureType[];
97
- textToImage: boolean;
98
- textToVideo: boolean;
99
- imageToVideo: boolean;
100
- textToVoice: boolean;
101
- textToText: boolean;
102
- }
103
-
104
- // =============================================================================
105
- // Feature Input Data
106
- // =============================================================================
107
-
108
- export interface ImageFeatureInputData {
109
- imageBase64: string;
110
- targetImageBase64?: string;
111
- prompt?: string;
112
- options?: Record<string, unknown>;
113
- }
114
-
115
- export interface VideoFeatureInputData {
116
- sourceImageBase64?: string;
117
- targetImageBase64?: string;
118
- prompt?: string;
119
- options?: Record<string, unknown>;
120
- }
121
-
122
- // =============================================================================
123
- // Provider Sub-Interfaces (Interface Segregation Principle)
124
- // =============================================================================
125
-
126
- export interface IAIProviderLifecycle {
127
- initialize(config: AIProviderConfig): void;
128
- isInitialized(): boolean;
129
- reset(): void;
130
- }
131
-
132
- export interface IAIProviderCapabilities {
133
- getCapabilities(): ProviderCapabilities;
134
- isFeatureSupported(feature: ImageFeatureType | VideoFeatureType): boolean;
135
- }
136
-
137
- export interface IAIProviderJobManager {
138
- submitJob(
139
- model: string,
140
- input: Record<string, unknown>,
141
- ): Promise<JobSubmission>;
142
- getJobStatus(model: string, requestId: string): Promise<JobStatus>;
143
- getJobResult<T = unknown>(model: string, requestId: string): Promise<T>;
144
- }
145
-
146
- export interface IAIProviderExecutor {
147
- subscribe<T = unknown>(
148
- model: string,
149
- input: Record<string, unknown>,
150
- options?: SubscribeOptions<T>,
151
- ): Promise<T>;
152
- run<T = unknown>(
153
- model: string,
154
- input: Record<string, unknown>,
155
- options?: RunOptions,
156
- ): Promise<T>;
157
- }
158
-
159
- export interface IAIProviderImageFeatures {
160
- getImageFeatureModel(feature: ImageFeatureType): string;
161
- buildImageFeatureInput(
162
- feature: ImageFeatureType,
163
- data: ImageFeatureInputData,
164
- ): Record<string, unknown>;
165
- }
166
-
167
- export interface IAIProviderVideoFeatures {
168
- getVideoFeatureModel(feature: VideoFeatureType): string;
169
- buildVideoFeatureInput(
170
- feature: VideoFeatureType,
171
- data: VideoFeatureInputData,
172
- ): Record<string, unknown>;
173
- }
174
-
175
- // =============================================================================
176
- // Main Provider Interface
177
- // =============================================================================
178
-
179
- /**
180
- * Main AI Provider Interface
181
- * Composition of segregated interfaces following SOLID principles
182
- */
183
- export interface IAIProvider
184
- extends IAIProviderLifecycle,
185
- IAIProviderCapabilities,
186
- IAIProviderJobManager,
187
- IAIProviderExecutor,
188
- IAIProviderImageFeatures,
189
- IAIProviderVideoFeatures {
190
- readonly providerId: string;
191
- readonly providerName: string;
192
- }
8
+ // Main provider types from ai-provider.interface
9
+ export type {
10
+ // Feature Types
11
+ ImageFeatureType,
12
+ VideoFeatureType,
13
+ // Config
14
+ AIProviderConfig,
15
+ // Status
16
+ AIJobStatusType,
17
+ AILogEntry,
18
+ JobSubmission,
19
+ JobStatus,
20
+ // Progress
21
+ ProviderProgressInfo,
22
+ SubscribeOptions,
23
+ RunOptions,
24
+ // Capabilities
25
+ ProviderCapabilities,
26
+ // Input Data
27
+ ImageFeatureInputData,
28
+ VideoFeatureInputData,
29
+ // Main Provider Interface
30
+ IAIProvider,
31
+ } from "../../domain/interfaces/ai-provider.interface";
32
+
33
+ // Segregated provider sub-interfaces
34
+ export type { IAIProviderLifecycle } from "../../domain/interfaces/provider-lifecycle.interface";
35
+ export type { IAIProviderCapabilities } from "../../domain/interfaces/provider-capabilities.interface";
36
+ export type { IAIProviderJobManager } from "../../domain/interfaces/provider-job-manager.interface";
37
+ export type { IAIProviderExecutor } from "../../domain/interfaces/provider-executor.interface";
38
+ export type { IAIProviderImageFeatures } from "../../domain/interfaces/provider-image-features.interface";
39
+ export type { IAIProviderVideoFeatures } from "../../domain/interfaces/provider-video-features.interface";
@@ -1,102 +1,18 @@
1
1
  /**
2
2
  * Result Type Pattern for Functional Error Handling
3
- * Inspired by Rust's Result<T, E> type
3
+ * Re-exports from canonical domain source
4
4
  *
5
5
  * @module @umituz/react-native-ai-generation-content/core
6
6
  */
7
7
 
8
- /**
9
- * Success result containing a value of type T
10
- */
11
- export interface Success<T> {
12
- success: true;
13
- value: T;
14
- }
15
-
16
- /**
17
- * Failure result containing an error of type E
18
- */
19
- export interface Failure<E> {
20
- success: false;
21
- error: E;
22
- }
23
-
24
- /**
25
- * Result type that can be either Success or Failure
26
- * Forces explicit error handling at compile time
27
- */
28
- export type Result<T, E = string> = Success<T> | Failure<E>;
29
-
30
- /**
31
- * Create a successful result
32
- */
33
- export function success<T>(value: T): Success<T> {
34
- return { success: true, value };
35
- }
36
-
37
- /**
38
- * Create a failed result
39
- */
40
- export function failure<E>(error: E): Failure<E> {
41
- return { success: false, error };
42
- }
43
-
44
- /**
45
- * Type guard to check if result is successful
46
- */
47
- export function isSuccess<T, E>(result: Result<T, E>): result is Success<T> {
48
- return result.success === true;
49
- }
50
-
51
- /**
52
- * Type guard to check if result is a failure
53
- */
54
- export function isFailure<T, E>(result: Result<T, E>): result is Failure<E> {
55
- return result.success === false;
56
- }
57
-
58
- /**
59
- * Map a successful result to a new value
60
- */
61
- export function mapResult<T, U, E>(
62
- result: Result<T, E>,
63
- fn: (value: T) => U,
64
- ): Result<U, E> {
65
- if (isSuccess(result)) {
66
- return success(fn(result.value));
67
- }
68
- return result;
69
- }
70
-
71
- /**
72
- * Chain async operations on Result types
73
- */
74
- export async function andThen<T, U, E>(
75
- result: Result<T, E>,
76
- fn: (value: T) => Promise<Result<U, E>>,
77
- ): Promise<Result<U, E>> {
78
- if (isSuccess(result)) {
79
- return fn(result.value);
80
- }
81
- return result;
82
- }
83
-
84
- /**
85
- * Unwrap a result, throwing if it's a failure
86
- */
87
- export function unwrap<T, E>(result: Result<T, E>): T {
88
- if (isSuccess(result)) {
89
- return result.value;
90
- }
91
- throw new Error(`Called unwrap on a failure: ${String(result.error)}`);
92
- }
93
-
94
- /**
95
- * Unwrap a result or return a default value
96
- */
97
- export function unwrapOr<T, E>(result: Result<T, E>, defaultValue: T): T {
98
- if (isSuccess(result)) {
99
- return result.value;
100
- }
101
- return defaultValue;
102
- }
8
+ export type { Result, Success, Failure } from "../../domain/types/result.types";
9
+ export {
10
+ success,
11
+ failure,
12
+ isSuccess,
13
+ isFailure,
14
+ mapResult,
15
+ andThen,
16
+ unwrap,
17
+ unwrapOr,
18
+ } from "../../domain/types/result.types";
@@ -79,8 +79,8 @@ export interface StepDefinition<TConfig = unknown> {
79
79
  readonly component?: React.ComponentType<StepComponentProps>;
80
80
  }
81
81
 
82
- /** Flow Generation Status */
83
- export type FlowGenerationStatus = "idle" | "preparing" | "generating" | "completed" | "failed";
82
+ /** Flow Generation Status - subset of GenerationStatus */
83
+ export type FlowGenerationStatus = Extract<import("./generation.types").GenerationStatus, "idle" | "preparing" | "generating" | "completed" | "failed">;
84
84
 
85
85
  /** Flow State */
86
86
  export interface FlowState {
@@ -53,4 +53,5 @@ export interface GenerationRequest {
53
53
  userId?: string;
54
54
  capability?: GenerationCapability;
55
55
  onProgress?: (progress: GenerationProgress) => void;
56
+ signal?: AbortSignal;
56
57
  }
@@ -5,7 +5,7 @@
5
5
  * Uses provider registry internally - no need to pass FAL functions
6
6
  */
7
7
 
8
- import { useEffect, useRef, useCallback } from "react";
8
+ import { useEffect, useRef, useCallback, useMemo } from "react";
9
9
  import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
10
10
  import { QUEUE_STATUS, CREATION_STATUS } from "../../../../domain/constants/queue-status.constants";
11
11
  import {
@@ -43,9 +43,21 @@ export function useProcessingJobsPoller(
43
43
  const pollingRef = useRef<Set<string>>(new Set());
44
44
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);
45
45
 
46
- // Find creations that need polling
47
- const processingJobs = creations.filter(
48
- (c) => c.status === CREATION_STATUS.PROCESSING && c.requestId && c.model,
46
+ // Find creations that need polling - stabilize reference with useMemo
47
+ const processingJobIds = useMemo(
48
+ () => creations
49
+ .filter((c) => c.status === CREATION_STATUS.PROCESSING && c.requestId && c.model)
50
+ .map((c) => c.id)
51
+ .join(","),
52
+ [creations],
53
+ );
54
+
55
+ const processingJobs = useMemo(
56
+ () => creations.filter(
57
+ (c) => c.status === CREATION_STATUS.PROCESSING && c.requestId && c.model,
58
+ ),
59
+ // eslint-disable-next-line react-hooks/exhaustive-deps
60
+ [processingJobIds],
49
61
  );
50
62
 
51
63
  const pollJob = useCallback(
@@ -13,14 +13,11 @@ export interface GenerationOptions {
13
13
  }
14
14
 
15
15
  // ============================================================================
16
- // Generation Result
16
+ // Generation Result (re-exported from canonical source)
17
17
  // ============================================================================
18
18
 
19
- export interface GenerationResult<T> {
20
- success: boolean;
21
- data?: T;
22
- error?: string;
23
- }
19
+ import type { GenerationResult as _GenerationResult } from "../../../domain/entities/generation.types";
20
+ export type { GenerationResult } from "../../../domain/entities/generation.types";
24
21
 
25
22
  // ============================================================================
26
23
  // Input Types (by generation type)
@@ -86,5 +83,5 @@ export interface GenerationExecutor<TInput, TOutput> {
86
83
  model: string,
87
84
  input: TInput,
88
85
  options?: GenerationOptions,
89
- ): Promise<GenerationResult<TOutput>>;
86
+ ): Promise<_GenerationResult<TOutput>>;
90
87
  }
@@ -2,8 +2,8 @@ export {
2
2
  useAIGeneration,
3
3
  type UseAIGenerationProps,
4
4
  type UseAIGenerationReturn,
5
- type AlertMessages,
6
5
  } from "./presentation/useAIGeneration.hook";
6
+ export type { AlertMessages } from "../../presentation/hooks/generation/types";
7
7
 
8
8
  export { featureRegistry } from "./application/feature-registry";
9
9
  export { createGenerationStrategy } from "./application/generation-strategy.factory";
@@ -133,7 +133,8 @@ export const createFlowStore = (config: FlowStoreConfig) => {
133
133
  },
134
134
 
135
135
  updateProgress: (progress: number) => {
136
- set({ generationProgress: progress, generationStatus: progress > 0 ? "generating" : "preparing" });
136
+ const generationStatus = progress >= 100 ? "completed" : progress > 0 ? "generating" : "preparing";
137
+ set({ generationProgress: progress, generationStatus });
137
138
  },
138
139
 
139
140
  setResult: (result: unknown) => {
@@ -5,6 +5,8 @@
5
5
  */
6
6
 
7
7
  import { useGenerationOrchestrator } from "../../../presentation/hooks/generation/orchestrator";
8
+ import type { AlertMessages } from "../../../presentation/hooks/generation/types";
9
+ import { DEFAULT_ALERT_MESSAGES } from "../../../presentation/constants/alert-messages";
8
10
  import { createGenerationStrategy } from "../application/generation-strategy.factory";
9
11
 
10
12
  declare const __DEV__: boolean;
@@ -13,14 +15,6 @@ declare const __DEV__: boolean;
13
15
  // Types
14
16
  // ============================================================================
15
17
 
16
- export interface AlertMessages {
17
- networkError: string;
18
- policyViolation: string;
19
- saveFailed: string;
20
- creditFailed: string;
21
- unknown: string;
22
- }
23
-
24
18
  export interface UseAIGenerationProps {
25
19
  /** Feature ID from feature registry */
26
20
  featureId: string;
@@ -89,13 +83,7 @@ export function useAIGeneration(
89
83
  // Use orchestrator for lifecycle management
90
84
  const orchestrator = useGenerationOrchestrator(strategy, {
91
85
  userId,
92
- alertMessages: alertMessages || {
93
- networkError: "No internet connection",
94
- policyViolation: "Content policy violation",
95
- saveFailed: "Failed to save",
96
- creditFailed: "Failed to deduct credits",
97
- unknown: "An error occurred",
98
- },
86
+ alertMessages: alertMessages || DEFAULT_ALERT_MESSAGES,
99
87
  onSuccess,
100
88
  onError: onError ? (error) => onError(error.message) : undefined,
101
89
  onCreditsExhausted,
@@ -33,10 +33,9 @@ export function useGenerationPhase(options?: UseGenerationPhaseOptions): Generat
33
33
  const interval = setInterval(() => {
34
34
  const elapsed = Date.now() - startTimeRef.current;
35
35
 
36
- if (elapsed < queuedDuration) {
37
- setPhase("queued");
38
- } else {
36
+ if (elapsed >= queuedDuration) {
39
37
  setPhase("processing");
38
+ clearInterval(interval);
40
39
  }
41
40
  }, 1000);
42
41
 
@@ -123,7 +123,7 @@ export function usePhotoBlockingGeneration(
123
123
  }
124
124
 
125
125
  // Start blocking generation
126
- generate(input);
126
+ await generate(input);
127
127
  },
128
128
  [userId, scenario, persistence, generate],
129
129
  );
@@ -41,6 +41,7 @@ export function useVideoQueueGeneration(
41
41
  const requestIdRef = useRef<string | null>(null);
42
42
  const modelRef = useRef<string | null>(null);
43
43
  const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
44
+ const isGeneratingRef = useRef(false);
44
45
  const [isGenerating, setIsGenerating] = useState(false);
45
46
 
46
47
  // Cleanup polling on unmount
@@ -57,6 +58,7 @@ export function useVideoQueueGeneration(
57
58
  creationIdRef.current = null;
58
59
  requestIdRef.current = null;
59
60
  modelRef.current = null;
61
+ isGeneratingRef.current = false;
60
62
  setIsGenerating(false);
61
63
  }, []);
62
64
 
@@ -138,6 +140,8 @@ export function useVideoQueueGeneration(
138
140
  const startGeneration = useCallback(
139
141
  async (input: unknown, prompt: string) => {
140
142
  if (!strategy.submitToQueue) { onError?.("Queue submission not available"); return; }
143
+ if (isGeneratingRef.current) return;
144
+ isGeneratingRef.current = true;
141
145
  setIsGenerating(true);
142
146
 
143
147
  // Save to Firestore FIRST (enables background visibility)
@@ -186,8 +186,10 @@ export const useWizardGeneration = (
186
186
  scenario,
187
187
  wizardData,
188
188
  isVideoMode,
189
- videoGeneration,
190
- photoGeneration,
189
+ videoGeneration.isGenerating,
190
+ videoGeneration.startGeneration,
191
+ photoGeneration.isGenerating,
192
+ photoGeneration.startGeneration,
191
193
  onError,
192
194
  ]);
193
195
 
@@ -29,7 +29,7 @@ export type {
29
29
  // Utils
30
30
  export {
31
31
  classifyError, isTransientError, isPermanentError, isResultNotReady, calculatePollingInterval,
32
- createPollingDelay, checkStatusForErrors, isJobComplete, isJobProcessing, isJobFailed,
32
+ checkStatusForErrors, isJobComplete, isJobProcessing, isJobFailed,
33
33
  validateResult, extractOutputUrl, extractOutputUrls, extractVideoUrl, extractThumbnailUrl,
34
34
  extractAudioUrl, extractImageUrls, cleanBase64, addBase64Prefix, preparePhoto, preparePhotos,
35
35
  isValidBase64, getBase64Size, getBase64SizeMB, prepareImage, createDevCallbacks, createFeatureUtils,