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

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.61.62",
3
+ "version": "1.61.63",
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",
@@ -0,0 +1,65 @@
1
+ import { buildWizardInput } from "../../infrastructure/strategies";
2
+ import type { WizardScenarioData } from "./wizard-generation.types";
3
+ import type { Scenario } from "../../../../scenarios/domain/scenario.types";
4
+ import type { GenerationAction } from "./wizard-state-machine";
5
+
6
+ declare const __DEV__: boolean;
7
+
8
+ interface ExecuteGenerationParams {
9
+ wizardData: WizardScenarioData;
10
+ scenario: Scenario;
11
+ isVideoMode: boolean;
12
+ isMountedRef: React.MutableRefObject<boolean>;
13
+ dispatch: React.Dispatch<GenerationAction>;
14
+ onError?: (error: string) => void;
15
+ videoGenerationFn: (input: any, prompt: string) => Promise<void>;
16
+ photoGenerationFn: (input: any, prompt: string) => Promise<void>;
17
+ }
18
+
19
+ export const executeWizardGeneration = async (params: ExecuteGenerationParams): Promise<void> => {
20
+ const {
21
+ wizardData,
22
+ scenario,
23
+ isVideoMode,
24
+ isMountedRef,
25
+ dispatch,
26
+ onError,
27
+ videoGenerationFn,
28
+ photoGenerationFn,
29
+ } = params;
30
+
31
+ try {
32
+ const input = await buildWizardInput(wizardData, scenario);
33
+
34
+ if (!isMountedRef.current) return;
35
+
36
+ if (!input) {
37
+ dispatch({ type: "ERROR", error: "Failed to build generation input" });
38
+ onError?.("Failed to build generation input");
39
+ return;
40
+ }
41
+
42
+ dispatch({ type: "START_GENERATION" });
43
+
44
+ if (__DEV__) {
45
+ console.log("[WizardGeneration] GENERATING -", isVideoMode ? "VIDEO" : "PHOTO");
46
+ }
47
+
48
+ const typedInput = input as { prompt?: string };
49
+ const generationFn = isVideoMode ? videoGenerationFn : photoGenerationFn;
50
+ await generationFn(input, typedInput.prompt || "");
51
+
52
+ if (isMountedRef.current) {
53
+ dispatch({ type: "COMPLETE" });
54
+ }
55
+ } catch (error: any) {
56
+ if (!isMountedRef.current) return;
57
+
58
+ const errorMsg = error?.message || "error.generation.unknown";
59
+ if (__DEV__) {
60
+ console.error("[WizardGeneration] Error:", errorMsg, error);
61
+ }
62
+ dispatch({ type: "ERROR", error: errorMsg });
63
+ onError?.(errorMsg);
64
+ }
65
+ };
@@ -0,0 +1,35 @@
1
+ export type GenerationStatus = "IDLE" | "PREPARING" | "GENERATING" | "ERROR" | "COMPLETED";
2
+
3
+ export interface GenerationState {
4
+ status: GenerationStatus;
5
+ error?: string;
6
+ }
7
+
8
+ export type GenerationAction =
9
+ | { type: "START_PREPARATION" }
10
+ | { type: "START_GENERATION" }
11
+ | { type: "COMPLETE" }
12
+ | { type: "ERROR"; error: string }
13
+ | { type: "RESET" };
14
+
15
+ export const generationReducer = (
16
+ state: GenerationState,
17
+ action: GenerationAction,
18
+ ): GenerationState => {
19
+ switch (action.type) {
20
+ case "START_PREPARATION":
21
+ return { status: "PREPARING" };
22
+ case "START_GENERATION":
23
+ return { status: "GENERATING" };
24
+ case "COMPLETE":
25
+ return { status: "COMPLETED" };
26
+ case "ERROR":
27
+ return { status: "ERROR", error: action.error };
28
+ case "RESET":
29
+ return { status: "IDLE" };
30
+ default:
31
+ return state;
32
+ }
33
+ };
34
+
35
+ export const INITIAL_STATE: GenerationState = { status: "IDLE" };
@@ -0,0 +1,13 @@
1
+ import type { Creation } from "../../../../creations/domain/entities/Creation";
2
+
3
+ export const isCreation = (result: unknown): result is Creation => {
4
+ if (!result || typeof result !== "object") return false;
5
+ const creation = result as Partial<Creation>;
6
+ return (
7
+ typeof creation.id === "string" &&
8
+ typeof creation.uri === "string" &&
9
+ typeof creation.type === "string" &&
10
+ creation.createdAt instanceof Date &&
11
+ typeof creation.isShared === "boolean"
12
+ );
13
+ };
@@ -1,19 +1,10 @@
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
1
  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 { QUEUE_STATUS } from "../../../../../domain/constants/queue-status.constants";
2
+ import { pollQueueStatus } from "./videoQueuePoller";
13
3
  import { DEFAULT_POLL_INTERVAL_MS } from "../../../../../infrastructure/constants/polling.constants";
14
4
  import type { CreationPersistence } from "../../infrastructure/utils/creation-persistence.util";
15
5
  import type { WizardStrategy } from "../../infrastructure/strategies/wizard-strategy.types";
16
6
  import type { WizardScenarioData } from "./wizard-generation.types";
7
+ import type { GenerationUrls } from "./generation-result.utils";
17
8
 
18
9
  declare const __DEV__: boolean;
19
10
 
@@ -31,9 +22,7 @@ export interface UseVideoQueueGenerationReturn {
31
22
  readonly startGeneration: (input: unknown, prompt: string) => Promise<void>;
32
23
  }
33
24
 
34
- export function useVideoQueueGeneration(
35
- props: UseVideoQueueGenerationProps,
36
- ): UseVideoQueueGenerationReturn {
25
+ export function useVideoQueueGeneration(props: UseVideoQueueGenerationProps): UseVideoQueueGenerationReturn {
37
26
  const { userId, scenario, persistence, strategy, onSuccess, onError } = props;
38
27
 
39
28
  const creationIdRef = useRef<string | null>(null);
@@ -44,7 +33,6 @@ export function useVideoQueueGeneration(
44
33
  const isPollingRef = useRef(false);
45
34
  const [isGenerating, setIsGenerating] = useState(false);
46
35
 
47
- // Cleanup polling on unmount
48
36
  useEffect(() => {
49
37
  return () => {
50
38
  if (pollingRef.current) {
@@ -74,7 +62,7 @@ export function useVideoQueueGeneration(
74
62
  videoUrl: urls.videoUrl,
75
63
  });
76
64
  } catch (err) {
77
- if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[VideoQueueGeneration] updateToCompleted error:", err);
65
+ if (__DEV__) console.error("[VideoQueueGeneration] updateToCompleted error:", err);
78
66
  }
79
67
  }
80
68
  resetRefs();
@@ -90,7 +78,7 @@ export function useVideoQueueGeneration(
90
78
  try {
91
79
  await persistence.updateToFailed(userId, creationId, errorMsg);
92
80
  } catch (err) {
93
- if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[VideoQueueGeneration] updateToFailed error:", err);
81
+ if (__DEV__) console.error("[VideoQueueGeneration] updateToFailed error:", err);
94
82
  }
95
83
  }
96
84
  resetRefs();
@@ -99,75 +87,52 @@ export function useVideoQueueGeneration(
99
87
  [userId, persistence, onError, resetRefs],
100
88
  );
101
89
 
102
- const pollQueueStatus = useCallback(async () => {
103
- // Guard against concurrent polls
104
- if (isPollingRef.current) return;
105
-
90
+ const pollStatus = useCallback(async () => {
106
91
  const requestId = requestIdRef.current;
107
92
  const model = modelRef.current;
108
- const provider = providerRegistry.getActiveProvider();
109
- if (!requestId || !model || !provider) return;
110
-
111
- isPollingRef.current = true;
112
- try {
113
- const status = await provider.getJobStatus(model, requestId);
114
- if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[VideoQueueGeneration] Poll:", status.status);
115
-
116
- if (status.status === QUEUE_STATUS.COMPLETED || status.status === QUEUE_STATUS.FAILED) {
117
- if (pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; }
118
- if (status.status === QUEUE_STATUS.COMPLETED) {
119
- try {
120
- const result = await provider.getJobResult<FalResult>(model, requestId);
121
- await handleComplete(extractResultUrl(result));
122
- } catch (resultErr) {
123
- // Handle errors when getting/extracting result (e.g., ValidationError, content policy)
124
- const errorMessage = resultErr instanceof Error ? resultErr.message : "Generation failed";
125
- if (typeof __DEV__ !== "undefined" && __DEV__) {
126
- console.error("[VideoQueueGeneration] Result error:", errorMessage);
127
- }
128
- await handleError(errorMessage);
129
- }
130
- } else {
131
- await handleError("Generation failed");
132
- }
133
- }
134
- } catch (err) {
135
- // Handle polling errors - stop polling and show error to user
136
- if (pollingRef.current) { clearInterval(pollingRef.current); pollingRef.current = null; }
137
- const errorMessage = err instanceof Error ? err.message : "Generation failed";
138
- if (typeof __DEV__ !== "undefined" && __DEV__) {
139
- console.error("[VideoQueueGeneration] Poll error:", errorMessage);
140
- }
141
- await handleError(errorMessage);
142
- } finally {
143
- isPollingRef.current = false;
144
- }
93
+ if (!requestId || !model) return;
94
+
95
+ await pollQueueStatus({
96
+ requestId,
97
+ model,
98
+ isPollingRef,
99
+ pollingRef,
100
+ onComplete: handleComplete,
101
+ onError: handleError,
102
+ });
145
103
  }, [handleComplete, handleError]);
146
104
 
147
105
  const startGeneration = useCallback(
148
106
  async (input: unknown, prompt: string) => {
149
- if (!strategy.submitToQueue) { onError?.("Queue submission not available"); return; }
107
+ if (!strategy.submitToQueue) {
108
+ onError?.("Queue submission not available");
109
+ return;
110
+ }
150
111
  if (isGeneratingRef.current) return;
112
+
151
113
  isGeneratingRef.current = true;
152
114
  setIsGenerating(true);
153
115
 
154
- // Save to Firestore FIRST (enables background visibility)
155
116
  let creationId: string | null = null;
156
117
  if (userId && prompt) {
157
118
  try {
158
119
  creationId = await persistence.saveAsProcessing(userId, {
159
- scenarioId: scenario.id, scenarioTitle: scenario.title || scenario.id, prompt,
120
+ scenarioId: scenario.id,
121
+ scenarioTitle: scenario.title || scenario.id,
122
+ prompt,
160
123
  });
161
124
  creationIdRef.current = creationId;
162
- if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[VideoQueueGeneration] Saved:", creationId);
125
+ if (__DEV__) console.log("[VideoQueueGeneration] Saved:", creationId);
163
126
  } catch (err) {
164
- if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[VideoQueueGeneration] save error:", err);
127
+ if (__DEV__) console.error("[VideoQueueGeneration] save error:", err);
165
128
  }
166
129
  }
167
130
 
168
131
  const queueResult = await strategy.submitToQueue(input);
169
132
  if (!queueResult.success || !queueResult.requestId || !queueResult.model) {
170
- if (creationId && userId) await persistence.updateToFailed(userId, creationId, queueResult.error || "Queue submission failed");
133
+ if (creationId && userId) {
134
+ await persistence.updateToFailed(userId, creationId, queueResult.error || "Queue submission failed");
135
+ }
171
136
  setIsGenerating(false);
172
137
  onError?.(queueResult.error || "Queue submission failed");
173
138
  return;
@@ -176,21 +141,19 @@ export function useVideoQueueGeneration(
176
141
  requestIdRef.current = queueResult.requestId;
177
142
  modelRef.current = queueResult.model;
178
143
 
179
- // Update with requestId for background polling
180
144
  if (creationId && userId) {
181
145
  try {
182
146
  await persistence.updateRequestId(userId, creationId, queueResult.requestId, queueResult.model);
183
- if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[VideoQueueGeneration] Updated requestId:", queueResult.requestId);
147
+ if (__DEV__) console.log("[VideoQueueGeneration] Updated requestId:", queueResult.requestId);
184
148
  } catch (err) {
185
- if (typeof __DEV__ !== "undefined" && __DEV__) console.error("[VideoQueueGeneration] updateRequestId error:", err);
149
+ if (__DEV__) console.error("[VideoQueueGeneration] updateRequestId error:", err);
186
150
  }
187
151
  }
188
152
 
189
- pollingRef.current = setInterval(() => void pollQueueStatus(), DEFAULT_POLL_INTERVAL_MS);
190
- // Immediate poll to avoid waiting for first interval tick
191
- void pollQueueStatus();
153
+ pollingRef.current = setInterval(() => void pollStatus(), DEFAULT_POLL_INTERVAL_MS);
154
+ void pollStatus();
192
155
  },
193
- [userId, scenario, persistence, strategy, pollQueueStatus, onError],
156
+ [userId, scenario, persistence, strategy, pollStatus, onError],
194
157
  );
195
158
 
196
159
  return { isGenerating, startGeneration };
@@ -1,32 +1,9 @@
1
- /**
2
- * Wizard Flow Handlers Hook
3
- * Extracts callback handlers from WizardFlowContent
4
- */
5
-
6
1
  import { useCallback } from "react";
7
2
  import { AlertType, AlertMode, useAlert } from "@umituz/react-native-design-system";
8
3
  import { StepType, type StepDefinition } from "../../../../../domain/entities/flow-config.types";
9
4
  import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
10
5
  import type { Creation } from "../../../../creations/domain/entities/Creation";
11
-
12
- declare const __DEV__: boolean;
13
-
14
- /**
15
- * Type guard to check if result is a valid Creation object
16
- */
17
- function isCreation(result: unknown): result is Creation {
18
- if (!result || typeof result !== "object") {
19
- return false;
20
- }
21
- const creation = result as Partial<Creation>;
22
- return (
23
- typeof creation.id === "string" &&
24
- typeof creation.uri === "string" &&
25
- typeof creation.type === "string" &&
26
- creation.createdAt instanceof Date &&
27
- typeof creation.isShared === "boolean"
28
- );
29
- }
6
+ import { isCreation } from "./typeGuards";
30
7
 
31
8
  export interface UseWizardFlowHandlersProps {
32
9
  readonly currentStepIndex: number;
@@ -77,21 +54,8 @@ export function useWizardFlowHandlers(props: UseWizardFlowHandlersProps) {
77
54
 
78
55
  const handleGenerationComplete = useCallback(
79
56
  (result: unknown) => {
80
- if (typeof __DEV__ !== "undefined" && __DEV__) {
81
- console.log("[WizardFlowHandlers] Generation completed");
82
- }
83
57
  setResult(result);
84
-
85
- // Use type guard to safely check if result is a Creation
86
- if (isCreation(result)) {
87
- setCurrentCreation(result);
88
- } else {
89
- if (typeof __DEV__ !== "undefined" && __DEV__) {
90
- console.warn("[WizardFlowHandlers] Result is not a valid Creation object:", result);
91
- }
92
- setCurrentCreation(null);
93
- }
94
-
58
+ setCurrentCreation(isCreation(result) ? result : null);
95
59
  onGenerationComplete?.(result);
96
60
  if (!skipResultStep) nextStep();
97
61
  },
@@ -100,43 +64,17 @@ export function useWizardFlowHandlers(props: UseWizardFlowHandlersProps) {
100
64
 
101
65
  const handleGenerationError = useCallback(
102
66
  (errorMessage: string) => {
103
- // Ensure we have a meaningful error message
104
67
  const safeErrorMessage = errorMessage?.trim() || "error.generation.unknown";
105
- if (typeof __DEV__ !== "undefined" && __DEV__) {
106
- console.log("[WizardFlowHandlers] Generation error:", {
107
- original: errorMessage,
108
- safe: safeErrorMessage,
109
- });
110
- }
111
- // Translate error key if it looks like a translation key
112
- const displayMessage = safeErrorMessage.startsWith("error.")
113
- ? t(safeErrorMessage)
114
- : safeErrorMessage;
115
- // Show error alert to user
116
- alert.show(
117
- AlertType.ERROR,
118
- AlertMode.MODAL,
119
- t("common.error"),
120
- displayMessage,
121
- );
122
- // Notify parent component
68
+ const displayMessage = safeErrorMessage.startsWith("error.") ? t(safeErrorMessage) : safeErrorMessage;
69
+ alert.show(AlertType.ERROR, AlertMode.MODAL, t("common.error"), displayMessage);
123
70
  onGenerationError?.(safeErrorMessage);
124
- // Close the wizard
125
71
  onBack?.();
126
72
  },
127
73
  [alert, t, onGenerationError, onBack],
128
74
  );
129
75
 
130
76
  const handleDismissGenerating = useCallback(() => {
131
- if (typeof __DEV__ !== "undefined" && __DEV__) {
132
- console.log("[WizardFlowHandlers] Dismissing - generation continues");
133
- }
134
- alert.show(
135
- AlertType.INFO,
136
- AlertMode.TOAST,
137
- t("generator.backgroundTitle"),
138
- t("generator.backgroundMessage"),
139
- );
77
+ alert.show(AlertType.INFO, AlertMode.TOAST, t("generator.backgroundTitle"), t("generator.backgroundMessage"));
140
78
  onBack?.();
141
79
  }, [alert, t, onBack]);
142
80
 
@@ -147,18 +85,7 @@ export function useWizardFlowHandlers(props: UseWizardFlowHandlersProps) {
147
85
 
148
86
  const handleNextStep = useCallback(() => {
149
87
  const nextStepDef = flowSteps[currentStepIndex + 1];
150
- if (typeof __DEV__ !== "undefined" && __DEV__) {
151
- console.log("[WizardFlowHandlers] handleNextStep", {
152
- currentStepIndex,
153
- nextStepType: nextStepDef?.type,
154
- isGenerating: nextStepDef?.type === StepType.GENERATING,
155
- hasOnGenerationStart: !!onGenerationStart,
156
- });
157
- }
158
88
  if (nextStepDef?.type === StepType.GENERATING && onGenerationStart) {
159
- if (typeof __DEV__ !== "undefined" && __DEV__) {
160
- console.log("[WizardFlowHandlers] Calling onGenerationStart callback");
161
- }
162
89
  onGenerationStart(customData, nextStep);
163
90
  return;
164
91
  }
@@ -179,12 +106,7 @@ export function useWizardFlowHandlers(props: UseWizardFlowHandlersProps) {
179
106
  const success = await repository.rate(userId, currentCreation.id, rating, description);
180
107
  if (success) {
181
108
  setHasRated(true);
182
- alert.show(
183
- AlertType.SUCCESS,
184
- AlertMode.TOAST,
185
- t("result.rateSuccessTitle"),
186
- t("result.rateSuccessMessage"),
187
- );
109
+ alert.show(AlertType.SUCCESS, AlertMode.TOAST, t("result.rateSuccessTitle"), t("result.rateSuccessMessage"));
188
110
  }
189
111
  setShowRatingPicker(false);
190
112
  },
@@ -1,75 +1,16 @@
1
1
  /**
2
- * useWizardGeneration Hook
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
6
- *
7
- * Architecture: State machine pattern with useReducer
8
- * States: IDLE → PREPARING → GENERATING → COMPLETED/ERROR → IDLE
2
+ * Wizard Generation Hook
3
+ * Orchestrates wizard-based generation (Video: queue, Photo: blocking)
9
4
  */
10
5
 
11
6
  import { useEffect, useReducer, useMemo, useRef } from "react";
12
- import { createWizardStrategy, buildWizardInput } from "../../infrastructure/strategies";
7
+ import { createWizardStrategy } from "../../infrastructure/strategies";
13
8
  import { createCreationPersistence } from "../../infrastructure/utils/creation-persistence.util";
14
9
  import { useVideoQueueGeneration } from "./useVideoQueueGeneration";
15
10
  import { usePhotoBlockingGeneration } from "./usePhotoBlockingGeneration";
16
- import type {
17
- UseWizardGenerationProps,
18
- UseWizardGenerationReturn,
19
- } from "./wizard-generation.types";
20
-
21
- declare const __DEV__: boolean;
22
-
23
- /**
24
- * Generation orchestration states
25
- */
26
- type GenerationStatus =
27
- | "IDLE" // Not started
28
- | "PREPARING" // Building input
29
- | "GENERATING" // Generation in progress
30
- | "ERROR" // Failed (prevents retry)
31
- | "COMPLETED"; // Success
32
-
33
- /**
34
- * State machine state
35
- */
36
- interface GenerationState {
37
- status: GenerationStatus;
38
- error?: string;
39
- }
40
-
41
- /**
42
- * State machine actions
43
- */
44
- type GenerationAction =
45
- | { type: "START_PREPARATION" }
46
- | { type: "START_GENERATION" }
47
- | { type: "COMPLETE" }
48
- | { type: "ERROR"; error: string }
49
- | { type: "RESET" };
50
-
51
- /**
52
- * State machine reducer
53
- */
54
- const generationReducer = (
55
- state: GenerationState,
56
- action: GenerationAction,
57
- ): GenerationState => {
58
- switch (action.type) {
59
- case "START_PREPARATION":
60
- return { status: "PREPARING" };
61
- case "START_GENERATION":
62
- return { status: "GENERATING" };
63
- case "COMPLETE":
64
- return { status: "COMPLETED" };
65
- case "ERROR":
66
- return { status: "ERROR", error: action.error };
67
- case "RESET":
68
- return { status: "IDLE" };
69
- default:
70
- return state;
71
- }
72
- };
11
+ import { generationReducer, INITIAL_STATE } from "./generationStateMachine";
12
+ import { executeWizardGeneration } from "./generationExecutor";
13
+ import type { UseWizardGenerationProps, UseWizardGenerationReturn } from "./wizard-generation.types";
73
14
 
74
15
  export type {
75
16
  WizardOutputType,
@@ -78,9 +19,7 @@ export type {
78
19
  UseWizardGenerationReturn,
79
20
  } from "./wizard-generation.types";
80
21
 
81
- export const useWizardGeneration = (
82
- props: UseWizardGenerationProps,
83
- ): UseWizardGenerationReturn => {
22
+ export const useWizardGeneration = (props: UseWizardGenerationProps): UseWizardGenerationReturn => {
84
23
  const {
85
24
  scenario,
86
25
  wizardData,
@@ -93,10 +32,7 @@ export const useWizardGeneration = (
93
32
  onCreditsExhausted,
94
33
  } = props;
95
34
 
96
- // State machine: replaces multiple useRef flags
97
- const [state, dispatch] = useReducer(generationReducer, { status: "IDLE" });
98
-
99
- // Mounted ref to prevent state updates after unmount
35
+ const [state, dispatch] = useReducer(generationReducer, INITIAL_STATE);
100
36
  const isMountedRef = useRef(true);
101
37
 
102
38
  useEffect(() => {
@@ -107,14 +43,9 @@ export const useWizardGeneration = (
107
43
  }, []);
108
44
 
109
45
  const persistence = useMemo(() => createCreationPersistence(), []);
110
- const strategy = useMemo(
111
- () => createWizardStrategy({ scenario, creditCost }),
112
- [scenario, creditCost],
113
- );
114
-
46
+ const strategy = useMemo(() => createWizardStrategy({ scenario, creditCost }), [scenario, creditCost]);
115
47
  const isVideoMode = scenario.outputType === "video" && !!strategy.submitToQueue;
116
48
 
117
- // Video generation hook (queue-based)
118
49
  const videoGeneration = useVideoQueueGeneration({
119
50
  userId,
120
51
  scenario,
@@ -124,7 +55,6 @@ export const useWizardGeneration = (
124
55
  onError,
125
56
  });
126
57
 
127
- // Photo generation hook (blocking)
128
58
  const photoGeneration = usePhotoBlockingGeneration({
129
59
  userId,
130
60
  scenario,
@@ -136,67 +66,25 @@ export const useWizardGeneration = (
136
66
  onCreditsExhausted,
137
67
  });
138
68
 
139
- // Main effect: trigger generation when step becomes active
140
69
  useEffect(() => {
141
70
  const isAlreadyGenerating = videoGeneration.isGenerating || photoGeneration.isGenerating;
142
71
 
143
- // Start generation: Simple single condition using state machine
144
72
  if (isGeneratingStep && state.status === "IDLE" && !isAlreadyGenerating) {
145
73
  dispatch({ type: "START_PREPARATION" });
146
74
 
147
- if (typeof __DEV__ !== "undefined" && __DEV__) {
148
- console.log("[WizardGeneration] State: PREPARING");
149
- }
150
-
151
- buildWizardInput(wizardData, scenario)
152
- .then(async (input) => {
153
- // Check if component is still mounted
154
- if (!isMountedRef.current) return;
155
-
156
- if (!input) {
157
- dispatch({ type: "ERROR", error: "Failed to build generation input" });
158
- onError?.("Failed to build generation input");
159
- return;
160
- }
161
-
162
- dispatch({ type: "START_GENERATION" });
163
-
164
- const typedInput = input as { prompt?: string };
165
-
166
- if (typeof __DEV__ !== "undefined" && __DEV__) {
167
- console.log("[WizardGeneration] State: GENERATING");
168
- console.log("[WizardGeneration] Mode:", isVideoMode ? "VIDEO_QUEUE" : "PHOTO_BLOCKING");
169
- }
170
-
171
- if (isVideoMode) {
172
- await videoGeneration.startGeneration(input, typedInput.prompt || "");
173
- } else {
174
- await photoGeneration.startGeneration(input, typedInput.prompt || "");
175
- }
176
-
177
- // Check again before final state update
178
- if (isMountedRef.current) {
179
- dispatch({ type: "COMPLETE" });
180
- }
181
- })
182
- .catch((error) => {
183
- // Check if component is still mounted before error handling
184
- if (!isMountedRef.current) return;
185
-
186
- const errorMsg = error?.message || "error.generation.unknown";
187
- if (typeof __DEV__ !== "undefined" && __DEV__) {
188
- console.error("[WizardGeneration] Build input error:", errorMsg, error);
189
- }
190
- dispatch({ type: "ERROR", error: errorMsg });
191
- onError?.(errorMsg);
192
- });
75
+ executeWizardGeneration({
76
+ wizardData,
77
+ scenario,
78
+ isVideoMode,
79
+ isMountedRef,
80
+ dispatch,
81
+ onError,
82
+ videoGenerationFn: videoGeneration.startGeneration,
83
+ photoGenerationFn: photoGeneration.startGeneration,
84
+ });
193
85
  }
194
86
 
195
- // Reset state when leaving generating step
196
87
  if (!isGeneratingStep && state.status !== "IDLE") {
197
- if (typeof __DEV__ !== "undefined" && __DEV__) {
198
- console.log("[WizardGeneration] State: RESET");
199
- }
200
88
  dispatch({ type: "RESET" });
201
89
  }
202
90
  }, [
@@ -0,0 +1,59 @@
1
+ import { providerRegistry } from "../../../../../infrastructure/services/provider-registry.service";
2
+ import { extractResultUrl, type FalResult, type GenerationUrls } from "./generation-result.utils";
3
+ import { QUEUE_STATUS } from "../../../../../domain/constants/queue-status.constants";
4
+
5
+ declare const __DEV__: boolean;
6
+
7
+ interface PollParams {
8
+ requestId: string;
9
+ model: string;
10
+ isPollingRef: React.MutableRefObject<boolean>;
11
+ pollingRef: React.MutableRefObject<ReturnType<typeof setInterval> | null>;
12
+ onComplete: (urls: GenerationUrls) => Promise<void>;
13
+ onError: (error: string) => Promise<void>;
14
+ }
15
+
16
+ export const pollQueueStatus = async (params: PollParams): Promise<void> => {
17
+ const { requestId, model, isPollingRef, pollingRef, onComplete, onError } = params;
18
+
19
+ if (isPollingRef.current) return;
20
+
21
+ const provider = providerRegistry.getActiveProvider();
22
+ if (!provider) return;
23
+
24
+ isPollingRef.current = true;
25
+ try {
26
+ const status = await provider.getJobStatus(model, requestId);
27
+ if (__DEV__) console.log("[VideoQueueGeneration] Poll:", status.status);
28
+
29
+ if (status.status === QUEUE_STATUS.COMPLETED || status.status === QUEUE_STATUS.FAILED) {
30
+ if (pollingRef.current) {
31
+ clearInterval(pollingRef.current);
32
+ pollingRef.current = null;
33
+ }
34
+
35
+ if (status.status === QUEUE_STATUS.COMPLETED) {
36
+ try {
37
+ const result = await provider.getJobResult<FalResult>(model, requestId);
38
+ await onComplete(extractResultUrl(result));
39
+ } catch (resultErr) {
40
+ const errorMessage = resultErr instanceof Error ? resultErr.message : "Generation failed";
41
+ if (__DEV__) console.error("[VideoQueueGeneration] Result error:", errorMessage);
42
+ await onError(errorMessage);
43
+ }
44
+ } else {
45
+ await onError("Generation failed");
46
+ }
47
+ }
48
+ } catch (err) {
49
+ if (pollingRef.current) {
50
+ clearInterval(pollingRef.current);
51
+ pollingRef.current = null;
52
+ }
53
+ const errorMessage = err instanceof Error ? err.message : "Generation failed";
54
+ if (__DEV__) console.error("[VideoQueueGeneration] Poll error:", errorMessage);
55
+ await onError(errorMessage);
56
+ } finally {
57
+ isPollingRef.current = false;
58
+ }
59
+ };