@umituz/react-native-ai-generation-content 1.49.0 → 1.50.0

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.49.0",
3
+ "version": "1.50.0",
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",
@@ -43,6 +43,10 @@ export async function buildVideoInput(
43
43
  if (defaultPrompt) {
44
44
  basePrompt = defaultPrompt;
45
45
  } else {
46
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
47
+ console.error("[VideoStrategy] No prompt found for scenario:", scenario.id);
48
+ console.error("[VideoStrategy] Available defaults:", Object.keys(VIDEO_PROCESSING_PROMPTS));
49
+ }
46
50
  throw new Error("error.generation.promptRequired");
47
51
  }
48
52
  }
@@ -50,4 +50,7 @@ export const VIDEO_PROCESSING_PROMPTS: Record<string, string> = {
50
50
  "ai-kiss": "Create a romantic video where these two people share a gentle, loving kiss",
51
51
  "ai-hug": "Create a heartwarming video where these two people share a warm, affectionate hug",
52
52
  "image-to-video": "Animate this image with natural, smooth motion while preserving the original style",
53
+ "solo_renaissance_portrait": "Transform this person into an elegant Renaissance-style animated portrait with classical artistic movements and period-appropriate lighting",
54
+ "renaissance_portrait": "Transform this portrait into a majestic Renaissance-style animated painting with subtle classical movements",
55
+ "historical_portrait": "Animate this portrait in a historical style with period-appropriate subtle movements",
53
56
  };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Generation Result Utilities
3
+ * Shared utilities for extracting and processing generation results
4
+ */
5
+
6
+ export interface FalResult {
7
+ video?: { url?: string };
8
+ output?: string;
9
+ images?: Array<{ url?: string }>;
10
+ image?: { url?: string };
11
+ }
12
+
13
+ export interface GenerationUrls {
14
+ imageUrl?: string;
15
+ videoUrl?: string;
16
+ }
17
+
18
+ /**
19
+ * Extracts image/video URL from FAL result
20
+ * Handles various result formats from different FAL models
21
+ */
22
+ export function extractResultUrl(result: FalResult): GenerationUrls {
23
+ // Video result
24
+ if (result.video?.url) {
25
+ return { videoUrl: result.video.url };
26
+ }
27
+
28
+ // Output URL (some models return direct URL)
29
+ if (typeof result.output === "string" && result.output.startsWith("http")) {
30
+ if (result.output.includes(".mp4") || result.output.includes("video")) {
31
+ return { videoUrl: result.output };
32
+ }
33
+ return { imageUrl: result.output };
34
+ }
35
+
36
+ // Images array (most image models)
37
+ if (result.images?.[0]?.url) {
38
+ return { imageUrl: result.images[0].url };
39
+ }
40
+
41
+ // Single image
42
+ if (result.image?.url) {
43
+ return { imageUrl: result.image.url };
44
+ }
45
+
46
+ return {};
47
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * usePhotoBlockingGeneration Hook
3
+ * Handles photo generation via blocking execution
4
+ * - Uses orchestrator for synchronous generation
5
+ * - Waits for result before returning
6
+ * - Best for quick image operations (10-30 seconds)
7
+ */
8
+
9
+ import { useRef, useCallback } from "react";
10
+ import { useGenerationOrchestrator } from "../../../../../presentation/hooks/generation";
11
+ import type { CreationPersistence } from "../../infrastructure/utils/creation-persistence.util";
12
+ import type { WizardStrategy } from "../../infrastructure/strategies/wizard-strategy.types";
13
+ import type { WizardScenarioData } from "./wizard-generation.types";
14
+ import type { AlertMessages } from "../../../../../presentation/hooks/generation/types";
15
+
16
+ declare const __DEV__: boolean;
17
+
18
+ export interface UsePhotoBlockingGenerationProps {
19
+ readonly userId?: string;
20
+ readonly scenario: WizardScenarioData;
21
+ readonly persistence: CreationPersistence;
22
+ readonly strategy: WizardStrategy;
23
+ readonly alertMessages: AlertMessages;
24
+ readonly onSuccess?: (result: unknown) => void;
25
+ readonly onError?: (error: string) => void;
26
+ readonly onCreditsExhausted?: () => void;
27
+ }
28
+
29
+ export interface UsePhotoBlockingGenerationReturn {
30
+ readonly isGenerating: boolean;
31
+ readonly startGeneration: (input: unknown, prompt: string) => Promise<void>;
32
+ }
33
+
34
+ export function usePhotoBlockingGeneration(
35
+ props: UsePhotoBlockingGenerationProps,
36
+ ): UsePhotoBlockingGenerationReturn {
37
+ const {
38
+ userId,
39
+ scenario,
40
+ persistence,
41
+ strategy,
42
+ alertMessages,
43
+ onSuccess,
44
+ onError,
45
+ onCreditsExhausted,
46
+ } = props;
47
+
48
+ const creationIdRef = useRef<string | null>(null);
49
+
50
+ const handleSuccess = useCallback(
51
+ async (result: unknown) => {
52
+ const typedResult = result as { imageUrl?: string; videoUrl?: string };
53
+ const creationId = creationIdRef.current;
54
+
55
+ if (creationId && userId) {
56
+ try {
57
+ await persistence.updateToCompleted(userId, creationId, {
58
+ uri: typedResult.imageUrl || typedResult.videoUrl || "",
59
+ imageUrl: typedResult.imageUrl,
60
+ videoUrl: typedResult.videoUrl,
61
+ });
62
+ } catch (err) {
63
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
64
+ console.error("[PhotoBlockingGeneration] updateToCompleted error:", err);
65
+ }
66
+ }
67
+ }
68
+
69
+ creationIdRef.current = null;
70
+ onSuccess?.(result);
71
+ },
72
+ [userId, persistence, onSuccess],
73
+ );
74
+
75
+ const handleError = useCallback(
76
+ async (err: { message: string }) => {
77
+ const creationId = creationIdRef.current;
78
+
79
+ if (creationId && userId) {
80
+ try {
81
+ await persistence.updateToFailed(userId, creationId, err.message);
82
+ } catch (updateErr) {
83
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
84
+ console.error("[PhotoBlockingGeneration] updateToFailed error:", updateErr);
85
+ }
86
+ }
87
+ }
88
+
89
+ creationIdRef.current = null;
90
+ onError?.(err.message);
91
+ },
92
+ [userId, persistence, onError],
93
+ );
94
+
95
+ const { generate, isGenerating } = useGenerationOrchestrator(strategy, {
96
+ userId,
97
+ alertMessages,
98
+ onCreditsExhausted,
99
+ onSuccess: handleSuccess,
100
+ onError: handleError,
101
+ });
102
+
103
+ const startGeneration = useCallback(
104
+ async (input: unknown, prompt: string) => {
105
+ // Save to Firestore first
106
+ if (userId && prompt) {
107
+ try {
108
+ const creationId = await persistence.saveAsProcessing(userId, {
109
+ scenarioId: scenario.id,
110
+ scenarioTitle: scenario.title || scenario.id,
111
+ prompt,
112
+ });
113
+ creationIdRef.current = creationId;
114
+
115
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
116
+ console.log("[PhotoBlockingGeneration] Saved as processing:", creationId);
117
+ }
118
+ } catch (err) {
119
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
120
+ console.error("[PhotoBlockingGeneration] saveAsProcessing error:", err);
121
+ }
122
+ }
123
+ }
124
+
125
+ // Start blocking generation
126
+ generate(input);
127
+ },
128
+ [userId, scenario, persistence, generate],
129
+ );
130
+
131
+ return { isGenerating, startGeneration };
132
+ }
@@ -0,0 +1,169 @@
1
+ /**
2
+ * useVideoQueueGeneration Hook
3
+ * Handles video generation via FAL queue with background support
4
+ * - Submits to queue for non-blocking generation
5
+ * - Polls for completion status
6
+ * - Supports background generation (user can dismiss wizard)
7
+ */
8
+
9
+ import { useEffect, useRef, useCallback, useState } from "react";
10
+ import { providerRegistry } from "../../../../../infrastructure/services/provider-registry.service";
11
+ import { extractResultUrl, type FalResult, type GenerationUrls } from "./generation-result.utils";
12
+ import type { CreationPersistence } from "../../infrastructure/utils/creation-persistence.util";
13
+ import type { WizardStrategy } from "../../infrastructure/strategies/wizard-strategy.types";
14
+ import type { WizardScenarioData } from "./wizard-generation.types";
15
+
16
+ declare const __DEV__: boolean;
17
+
18
+ const POLL_INTERVAL_MS = 3000;
19
+
20
+ export interface UseVideoQueueGenerationProps {
21
+ readonly userId?: string;
22
+ readonly scenario: WizardScenarioData;
23
+ readonly persistence: CreationPersistence;
24
+ readonly strategy: WizardStrategy;
25
+ readonly onSuccess?: (result: unknown) => void;
26
+ readonly onError?: (error: string) => void;
27
+ }
28
+
29
+ export interface UseVideoQueueGenerationReturn {
30
+ readonly isGenerating: boolean;
31
+ readonly startGeneration: (input: unknown, prompt: string) => Promise<void>;
32
+ }
33
+
34
+ export function useVideoQueueGeneration(
35
+ props: UseVideoQueueGenerationProps,
36
+ ): UseVideoQueueGenerationReturn {
37
+ const { userId, scenario, persistence, strategy, onSuccess, onError } = props;
38
+
39
+ const creationIdRef = useRef<string | null>(null);
40
+ const requestIdRef = useRef<string | null>(null);
41
+ const modelRef = useRef<string | null>(null);
42
+ const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
43
+ const [isGenerating, setIsGenerating] = useState(false);
44
+
45
+ // Cleanup polling on unmount
46
+ useEffect(() => {
47
+ return () => {
48
+ if (pollingRef.current) {
49
+ clearInterval(pollingRef.current);
50
+ pollingRef.current = null;
51
+ }
52
+ };
53
+ }, []);
54
+
55
+ const resetRefs = useCallback(() => {
56
+ creationIdRef.current = null;
57
+ requestIdRef.current = null;
58
+ modelRef.current = null;
59
+ setIsGenerating(false);
60
+ }, []);
61
+
62
+ const handleComplete = useCallback(
63
+ async (urls: GenerationUrls) => {
64
+ const creationId = creationIdRef.current;
65
+ if (creationId && userId) {
66
+ try {
67
+ await persistence.updateToCompleted(userId, creationId, {
68
+ uri: urls.videoUrl || urls.imageUrl || "",
69
+ imageUrl: urls.imageUrl,
70
+ videoUrl: urls.videoUrl,
71
+ });
72
+ } catch (err) {
73
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[VideoQueueGeneration] updateToCompleted error:", err);
74
+ }
75
+ }
76
+ resetRefs();
77
+ onSuccess?.(urls);
78
+ },
79
+ [userId, persistence, onSuccess, resetRefs],
80
+ );
81
+
82
+ const handleError = useCallback(
83
+ async (errorMsg: string) => {
84
+ const creationId = creationIdRef.current;
85
+ if (creationId && userId) {
86
+ try {
87
+ await persistence.updateToFailed(userId, creationId, errorMsg);
88
+ } catch (err) {
89
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[VideoQueueGeneration] updateToFailed error:", err);
90
+ }
91
+ }
92
+ resetRefs();
93
+ onError?.(errorMsg);
94
+ },
95
+ [userId, persistence, onError, resetRefs],
96
+ );
97
+
98
+ const pollQueueStatus = useCallback(async () => {
99
+ const requestId = requestIdRef.current;
100
+ const model = modelRef.current;
101
+ const provider = providerRegistry.getActiveProvider();
102
+ if (!requestId || !model || !provider) return;
103
+
104
+ try {
105
+ const status = await provider.getJobStatus(model, requestId);
106
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[VideoQueueGeneration] Poll:", status.status);
107
+
108
+ if (status.status === "COMPLETED" || status.status === "FAILED") {
109
+ if (pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; }
110
+ if (status.status === "COMPLETED") {
111
+ const result = await provider.getJobResult<FalResult>(model, requestId);
112
+ await handleComplete(extractResultUrl(result));
113
+ } else {
114
+ await handleError("Generation failed");
115
+ }
116
+ }
117
+ } catch (err) {
118
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[VideoQueueGeneration] Poll error:", err);
119
+ }
120
+ }, [handleComplete, handleError]);
121
+
122
+ const startGeneration = useCallback(
123
+ async (input: unknown, prompt: string) => {
124
+ if (!strategy.submitToQueue) { onError?.("Queue submission not available"); return; }
125
+ setIsGenerating(true);
126
+
127
+ // Save to Firestore FIRST (enables background visibility)
128
+ let creationId: string | null = null;
129
+ if (userId && prompt) {
130
+ try {
131
+ creationId = await persistence.saveAsProcessing(userId, {
132
+ scenarioId: scenario.id, scenarioTitle: scenario.title || scenario.id, prompt,
133
+ });
134
+ creationIdRef.current = creationId;
135
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[VideoQueueGeneration] Saved:", creationId);
136
+ } catch (err) {
137
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[VideoQueueGeneration] save error:", err);
138
+ }
139
+ }
140
+
141
+ const queueResult = await strategy.submitToQueue(input);
142
+ if (!queueResult.success || !queueResult.requestId || !queueResult.model) {
143
+ if (creationId && userId) await persistence.updateToFailed(userId, creationId, queueResult.error || "Queue submission failed");
144
+ setIsGenerating(false);
145
+ onError?.(queueResult.error || "Queue submission failed");
146
+ return;
147
+ }
148
+
149
+ requestIdRef.current = queueResult.requestId;
150
+ modelRef.current = queueResult.model;
151
+
152
+ // Update with requestId for background polling
153
+ if (creationId && userId) {
154
+ try {
155
+ await persistence.updateRequestId(userId, creationId, queueResult.requestId, queueResult.model);
156
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[VideoQueueGeneration] Updated requestId:", queueResult.requestId);
157
+ } catch (err) {
158
+ if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[VideoQueueGeneration] updateRequestId error:", err);
159
+ }
160
+ }
161
+
162
+ pollingRef.current = setInterval(() => void pollQueueStatus(), POLL_INTERVAL_MS);
163
+ void pollQueueStatus();
164
+ },
165
+ [userId, scenario, persistence, strategy, pollQueueStatus, onError],
166
+ );
167
+
168
+ return { isGenerating, startGeneration };
169
+ }
@@ -1,16 +1,15 @@
1
1
  /**
2
2
  * useWizardGeneration Hook
3
- * Wizard generation with Firestore persistence and background support
4
- * - Uses queue submission for videos (enables true background generation)
5
- * - Uses blocking execution for images
6
- * - Saves requestId/model to Firestore for background polling
3
+ * Orchestrates wizard-based generation by delegating to appropriate mode:
4
+ * - Video: Queue-based generation with background support
5
+ * - Photo: Blocking execution for quick results
7
6
  */
8
7
 
9
- import { useEffect, useRef, useMemo, useCallback, useState } from "react";
10
- import { useGenerationOrchestrator } from "../../../../../presentation/hooks/generation";
11
- import { providerRegistry } from "../../../../../infrastructure/services/provider-registry.service";
8
+ import { useEffect, useRef, useMemo } from "react";
12
9
  import { createWizardStrategy, buildWizardInput } from "../../infrastructure/strategies";
13
10
  import { createCreationPersistence } from "../../infrastructure/utils/creation-persistence.util";
11
+ import { useVideoQueueGeneration } from "./useVideoQueueGeneration";
12
+ import { usePhotoBlockingGeneration } from "./usePhotoBlockingGeneration";
14
13
  import type {
15
14
  UseWizardGenerationProps,
16
15
  UseWizardGenerationReturn,
@@ -18,8 +17,6 @@ import type {
18
17
 
19
18
  declare const __DEV__: boolean;
20
19
 
21
- const POLL_INTERVAL_MS = 3000;
22
-
23
20
  export type {
24
21
  WizardOutputType,
25
22
  WizardScenarioData,
@@ -27,32 +24,6 @@ export type {
27
24
  UseWizardGenerationReturn,
28
25
  } from "./wizard-generation.types";
29
26
 
30
- interface FalResult {
31
- video?: { url?: string };
32
- output?: string;
33
- images?: Array<{ url?: string }>;
34
- image?: { url?: string };
35
- }
36
-
37
- function extractResultUrl(result: FalResult): { imageUrl?: string; videoUrl?: string } {
38
- if (result.video?.url) {
39
- return { videoUrl: result.video.url };
40
- }
41
- if (typeof result.output === "string" && result.output.startsWith("http")) {
42
- if (result.output.includes(".mp4") || result.output.includes("video")) {
43
- return { videoUrl: result.output };
44
- }
45
- return { imageUrl: result.output };
46
- }
47
- if (result.images?.[0]?.url) {
48
- return { imageUrl: result.images[0].url };
49
- }
50
- if (result.image?.url) {
51
- return { imageUrl: result.image.url };
52
- }
53
- return {};
54
- }
55
-
56
27
  export const useWizardGeneration = (
57
28
  props: UseWizardGenerationProps,
58
29
  ): UseWizardGenerationReturn => {
@@ -69,11 +40,6 @@ export const useWizardGeneration = (
69
40
  } = props;
70
41
 
71
42
  const hasStarted = useRef(false);
72
- const creationIdRef = useRef<string | null>(null);
73
- const requestIdRef = useRef<string | null>(null);
74
- const modelRef = useRef<string | null>(null);
75
- const pollingRef = useRef<ReturnType<typeof setInterval> | null>(null);
76
- const [isGenerating, setIsGenerating] = useState(false);
77
43
 
78
44
  const persistence = useMemo(() => createCreationPersistence(), []);
79
45
  const strategy = useMemo(
@@ -81,158 +47,35 @@ export const useWizardGeneration = (
81
47
  [scenario, creditCost],
82
48
  );
83
49
 
84
- // Cleanup polling on unmount
85
- useEffect(() => {
86
- return () => {
87
- if (pollingRef.current) {
88
- clearInterval(pollingRef.current);
89
- pollingRef.current = null;
90
- }
91
- };
92
- }, []);
93
-
94
- const handleQueueComplete = useCallback(
95
- async (urls: { imageUrl?: string; videoUrl?: string }) => {
96
- const creationId = creationIdRef.current;
97
-
98
- if (creationId && userId) {
99
- try {
100
- await persistence.updateToCompleted(userId, creationId, {
101
- uri: urls.videoUrl || urls.imageUrl || "",
102
- imageUrl: urls.imageUrl,
103
- videoUrl: urls.videoUrl,
104
- });
105
- } catch (err) {
106
- if (typeof __DEV__ !== "undefined" && __DEV__) {
107
- console.error("[useWizardGeneration] updateToCompleted error:", err);
108
- }
109
- }
110
- }
111
-
112
- creationIdRef.current = null;
113
- requestIdRef.current = null;
114
- modelRef.current = null;
115
- setIsGenerating(false);
116
- onSuccess?.(urls);
117
- },
118
- [userId, persistence, onSuccess],
119
- );
120
-
121
- const handleQueueError = useCallback(
122
- async (errorMsg: string) => {
123
- const creationId = creationIdRef.current;
50
+ const isVideoMode = scenario.outputType === "video" && !!strategy.submitToQueue;
124
51
 
125
- if (creationId && userId) {
126
- try {
127
- await persistence.updateToFailed(userId, creationId, errorMsg);
128
- } catch (err) {
129
- if (typeof __DEV__ !== "undefined" && __DEV__) {
130
- console.error("[useWizardGeneration] updateToFailed error:", err);
131
- }
132
- }
133
- }
134
-
135
- creationIdRef.current = null;
136
- requestIdRef.current = null;
137
- modelRef.current = null;
138
- setIsGenerating(false);
139
- onError?.(errorMsg);
140
- },
141
- [userId, persistence, onError],
142
- );
143
-
144
- const pollQueueStatus = useCallback(async () => {
145
- const requestId = requestIdRef.current;
146
- const model = modelRef.current;
147
- const provider = providerRegistry.getActiveProvider();
148
-
149
- if (!requestId || !model || !provider) return;
150
-
151
- try {
152
- const status = await provider.getJobStatus(model, requestId);
153
-
154
- if (typeof __DEV__ !== "undefined" && __DEV__) {
155
- console.log("[useWizardGeneration] Poll status:", status.status);
156
- }
157
-
158
- if (status.status === "COMPLETED") {
159
- if (pollingRef.current) {
160
- clearInterval(pollingRef.current);
161
- pollingRef.current = null;
162
- }
163
- const result = await provider.getJobResult<FalResult>(model, requestId);
164
- const urls = extractResultUrl(result);
165
- await handleQueueComplete(urls);
166
- } else if (status.status === "FAILED") {
167
- if (pollingRef.current) {
168
- clearInterval(pollingRef.current);
169
- pollingRef.current = null;
170
- }
171
- await handleQueueError("Generation failed");
172
- }
173
- } catch (err) {
174
- if (typeof __DEV__ !== "undefined" && __DEV__) {
175
- console.error("[useWizardGeneration] Poll error:", err);
176
- }
177
- }
178
- }, [handleQueueComplete, handleQueueError]);
179
-
180
- // For images: use blocking execution with orchestrator
181
- const handleBlockingSuccess = useCallback(
182
- async (result: unknown) => {
183
- const typedResult = result as { imageUrl?: string; videoUrl?: string };
184
- const creationId = creationIdRef.current;
185
-
186
- if (creationId && userId) {
187
- try {
188
- await persistence.updateToCompleted(userId, creationId, {
189
- uri: typedResult.imageUrl || typedResult.videoUrl || "",
190
- imageUrl: typedResult.imageUrl,
191
- videoUrl: typedResult.videoUrl,
192
- });
193
- } catch (err) {
194
- if (typeof __DEV__ !== "undefined" && __DEV__) {
195
- console.error("[useWizardGeneration] updateToCompleted error:", err);
196
- }
197
- }
198
- }
199
-
200
- creationIdRef.current = null;
201
- onSuccess?.(result);
202
- },
203
- [userId, persistence, onSuccess],
204
- );
205
-
206
- const handleBlockingError = useCallback(
207
- async (err: { message: string }) => {
208
- const creationId = creationIdRef.current;
209
-
210
- if (creationId && userId) {
211
- try {
212
- await persistence.updateToFailed(userId, creationId, err.message);
213
- } catch (updateErr) {
214
- if (typeof __DEV__ !== "undefined" && __DEV__) {
215
- console.error("[useWizardGeneration] updateToFailed error:", updateErr);
216
- }
217
- }
218
- }
219
-
220
- creationIdRef.current = null;
221
- onError?.(err.message);
222
- },
223
- [userId, persistence, onError],
224
- );
52
+ // Video generation hook (queue-based)
53
+ const videoGeneration = useVideoQueueGeneration({
54
+ userId,
55
+ scenario,
56
+ persistence,
57
+ strategy,
58
+ onSuccess,
59
+ onError,
60
+ });
225
61
 
226
- const { generate, isGenerating: isBlockingGenerating } = useGenerationOrchestrator(strategy, {
62
+ // Photo generation hook (blocking)
63
+ const photoGeneration = usePhotoBlockingGeneration({
227
64
  userId,
65
+ scenario,
66
+ persistence,
67
+ strategy,
228
68
  alertMessages,
69
+ onSuccess,
70
+ onError,
229
71
  onCreditsExhausted,
230
- onSuccess: handleBlockingSuccess,
231
- onError: handleBlockingError,
232
72
  });
233
73
 
74
+ // Main effect: trigger generation when step becomes active
234
75
  useEffect(() => {
235
- if (isGeneratingStep && !hasStarted.current && !isGenerating && !isBlockingGenerating) {
76
+ const isAlreadyGenerating = videoGeneration.isGenerating || photoGeneration.isGenerating;
77
+
78
+ if (isGeneratingStep && !hasStarted.current && !isAlreadyGenerating) {
236
79
  hasStarted.current = true;
237
80
 
238
81
  buildWizardInput(wizardData, scenario)
@@ -244,86 +87,21 @@ export const useWizardGeneration = (
244
87
  }
245
88
 
246
89
  const typedInput = input as { prompt?: string };
247
- const useQueueMode = scenario.outputType === "video" && !!strategy.submitToQueue;
248
90
 
249
91
  if (typeof __DEV__ !== "undefined" && __DEV__) {
250
- console.log("[useWizardGeneration] Mode:", useQueueMode ? "QUEUE" : "BLOCKING");
92
+ console.log("[WizardGeneration] Mode:", isVideoMode ? "VIDEO_QUEUE" : "PHOTO_BLOCKING");
251
93
  }
252
94
 
253
- if (useQueueMode && strategy.submitToQueue) {
254
- // Queue mode for videos
255
- setIsGenerating(true);
256
-
257
- // Submit to queue first to get requestId
258
- const queueResult = await strategy.submitToQueue(input);
259
-
260
- if (!queueResult.success || !queueResult.requestId || !queueResult.model) {
261
- hasStarted.current = false;
262
- setIsGenerating(false);
263
- onError?.(queueResult.error || "Queue submission failed");
264
- return;
265
- }
266
-
267
- requestIdRef.current = queueResult.requestId;
268
- modelRef.current = queueResult.model;
269
-
270
- // Save to Firestore with requestId and model
271
- if (userId && typedInput.prompt) {
272
- try {
273
- const creationId = await persistence.saveAsProcessing(userId, {
274
- scenarioId: scenario.id,
275
- scenarioTitle: scenario.title || scenario.id,
276
- prompt: typedInput.prompt,
277
- requestId: queueResult.requestId,
278
- model: queueResult.model,
279
- });
280
- creationIdRef.current = creationId;
281
-
282
- if (typeof __DEV__ !== "undefined" && __DEV__) {
283
- console.log("[useWizardGeneration] Saved with requestId:", {
284
- creationId,
285
- requestId: queueResult.requestId,
286
- });
287
- }
288
- } catch (err) {
289
- if (typeof __DEV__ !== "undefined" && __DEV__) {
290
- console.error("[useWizardGeneration] saveAsProcessing error:", err);
291
- }
292
- }
293
- }
294
-
295
- // Start polling for completion
296
- pollingRef.current = setInterval(() => {
297
- void pollQueueStatus();
298
- }, POLL_INTERVAL_MS);
299
-
300
- // Initial poll
301
- void pollQueueStatus();
95
+ if (isVideoMode) {
96
+ await videoGeneration.startGeneration(input, typedInput.prompt || "");
302
97
  } else {
303
- // Blocking mode for images
304
- if (userId && typedInput.prompt) {
305
- try {
306
- const creationId = await persistence.saveAsProcessing(userId, {
307
- scenarioId: scenario.id,
308
- scenarioTitle: scenario.title || scenario.id,
309
- prompt: typedInput.prompt,
310
- });
311
- creationIdRef.current = creationId;
312
-
313
- if (typeof __DEV__ !== "undefined" && __DEV__) {
314
- console.log("[useWizardGeneration] Saved as processing:", creationId);
315
- }
316
- } catch (err) {
317
- if (typeof __DEV__ !== "undefined" && __DEV__) {
318
- console.error("[useWizardGeneration] saveAsProcessing error:", err);
319
- }
320
- }
321
- }
322
-
323
- generate(input);
98
+ await photoGeneration.startGeneration(input, typedInput.prompt || "");
324
99
  }
325
100
  })
326
101
  .catch((error) => {
102
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
103
+ console.error("[WizardGeneration] Build input error:", error.message);
104
+ }
327
105
  hasStarted.current = false;
328
106
  onError?.(error.message);
329
107
  });
@@ -336,15 +114,13 @@ export const useWizardGeneration = (
336
114
  isGeneratingStep,
337
115
  scenario,
338
116
  wizardData,
339
- isGenerating,
340
- isBlockingGenerating,
341
- generate,
117
+ isVideoMode,
118
+ videoGeneration,
119
+ photoGeneration,
342
120
  onError,
343
- userId,
344
- persistence,
345
- strategy,
346
- pollQueueStatus,
347
121
  ]);
348
122
 
349
- return { isGenerating: isGenerating || isBlockingGenerating };
123
+ return {
124
+ isGenerating: videoGeneration.isGenerating || photoGeneration.isGenerating,
125
+ };
350
126
  };