@umituz/react-native-ai-generation-content 1.61.62 → 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.
- package/package.json +1 -1
- package/src/core/index.ts +1 -1
- package/src/domain/entities/index.ts +1 -1
- package/src/domain/interfaces/ai-provider.interface.ts +1 -1
- package/src/domain/interfaces/index.ts +1 -1
- package/src/domains/background/domain/entities/index.ts +1 -0
- package/src/domains/background/domain/interfaces/index.ts +1 -0
- package/src/{domain → domains/background/domain}/interfaces/provider-job-manager.interface.ts +1 -1
- package/src/domains/background/domain/types/background-generation.types.ts +28 -0
- package/src/domains/background/infrastructure/executors/backgroundJobExecutor.ts +105 -0
- package/src/{infrastructure → domains/background/infrastructure}/services/job-poller-factory.ts +1 -1
- package/src/{infrastructure → domains/background/infrastructure}/services/job-poller.service.ts +1 -1
- package/src/{infrastructure → domains/background/infrastructure}/services/job-poller.types.ts +2 -2
- package/src/{infrastructure → domains/background/infrastructure}/utils/polling-interval.util.ts +1 -1
- package/src/{infrastructure → domains/background/infrastructure}/utils/status-checker.util.ts +1 -1
- package/src/domains/background/presentation/hooks/use-background-generation.ts +97 -0
- package/src/domains/creations/presentation/components/PendingJobsSection.tsx +1 -1
- package/src/domains/generation/wizard/presentation/hooks/generationExecutor.ts +65 -0
- package/src/domains/generation/wizard/presentation/hooks/generationStateMachine.ts +35 -0
- package/src/domains/generation/wizard/presentation/hooks/typeGuards.ts +13 -0
- package/src/domains/generation/wizard/presentation/hooks/useVideoQueueGeneration.ts +34 -71
- package/src/domains/generation/wizard/presentation/hooks/useWizardFlowHandlers.ts +6 -84
- package/src/domains/generation/wizard/presentation/hooks/useWizardGeneration.ts +19 -131
- package/src/domains/generation/wizard/presentation/hooks/videoQueuePoller.ts +59 -0
- package/src/domains/image-to-video/presentation/hooks/imageToVideoStrategy.ts +77 -0
- package/src/domains/image-to-video/presentation/hooks/useImageToVideoFeature.ts +102 -0
- package/src/domains/scenarios/presentation/containers/CategoryNavigationContainer.tsx +4 -80
- package/src/{features → domains}/text-to-image/infrastructure/services/text-to-image-executor.ts +2 -82
- package/src/domains/text-to-image/infrastructure/utils/imageResultExtractor.ts +58 -0
- package/src/domains/text-to-video/presentation/hooks/textToVideoStrategy.ts +75 -0
- package/src/domains/text-to-video/presentation/hooks/useTextToVideoFeature.ts +120 -0
- package/src/exports/features.ts +12 -12
- package/src/presentation/components/PendingJobCard.tsx +1 -1
- package/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.ts +0 -186
- package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +0 -186
- package/src/presentation/hooks/use-background-generation.ts +0 -185
- /package/src/{domain → domains/background/domain}/entities/job.types.ts +0 -0
- /package/src/{infrastructure → domains/background/infrastructure}/utils/result-validator.util.ts +0 -0
- /package/src/{presentation → domains/background/presentation}/hooks/use-pending-jobs.ts +0 -0
- /package/src/{features → domains}/image-to-video/README.md +0 -0
- /package/src/{features → domains}/image-to-video/domain/constants/animation.constants.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/constants/duration.constants.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/constants/form.constants.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/constants/index.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/constants/music.constants.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/index.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/types/animation.types.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/types/config.types.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/types/duration.types.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/types/form.types.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/types/image-to-video.types.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/types/index.ts +0 -0
- /package/src/{features → domains}/image-to-video/domain/types/music.types.ts +0 -0
- /package/src/{features → domains}/image-to-video/index.ts +0 -0
- /package/src/{features → domains}/image-to-video/infrastructure/index.ts +0 -0
- /package/src/{features → domains}/image-to-video/infrastructure/services/image-to-video-executor.ts +0 -0
- /package/src/{features → domains}/image-to-video/infrastructure/services/index.ts +0 -0
- /package/src/{features → domains}/image-to-video/presentation/components/AddMoreCard.tsx +0 -0
- /package/src/{features → domains}/image-to-video/presentation/components/AnimationStyleSelector.tsx +0 -0
- /package/src/{features → domains}/image-to-video/presentation/components/DurationSelector.tsx +0 -0
- /package/src/{features → domains}/image-to-video/presentation/components/EmptyGridState.tsx +0 -0
- /package/src/{features → domains}/image-to-video/presentation/components/GridImageItem.tsx +0 -0
- /package/src/{features → domains}/image-to-video/presentation/components/ImageSelectionGrid.styles.ts +0 -0
- /package/src/{features → domains}/image-to-video/presentation/components/ImageSelectionGrid.tsx +0 -0
- /package/src/{features → domains}/image-to-video/presentation/components/ImageSelectionGrid.types.ts +0 -0
- /package/src/{features → domains}/image-to-video/presentation/components/MusicMoodSelector.tsx +0 -0
- /package/src/{features → domains}/image-to-video/presentation/components/index.ts +0 -0
- /package/src/{features → domains}/image-to-video/presentation/hooks/image-to-video-feature.types.ts +0 -0
- /package/src/{features → domains}/image-to-video/presentation/hooks/index.ts +0 -0
- /package/src/{features → domains}/image-to-video/presentation/hooks/useFormState.ts +0 -0
- /package/src/{features → domains}/image-to-video/presentation/hooks/useGeneration.ts +0 -0
- /package/src/{features → domains}/image-to-video/presentation/hooks/useImageToVideoForm.ts +0 -0
- /package/src/{features → domains}/image-to-video/presentation/index.ts +0 -0
- /package/src/{features → domains}/image-to-video/presentation/screens/ImageToVideoWizardFlow.tsx +0 -0
- /package/src/{features → domains}/shared/index.ts +0 -0
- /package/src/{features → domains}/shared/presentation/components/AutoSkipPreview.tsx +0 -0
- /package/src/{features → domains}/shared/presentation/components/index.ts +0 -0
- /package/src/{features → domains}/shared/presentation/utils/index.ts +0 -0
- /package/src/{features → domains}/shared/presentation/utils/wizard-flow.utils.ts +0 -0
- /package/src/{features → domains}/text-to-image/README.md +0 -0
- /package/src/{features → domains}/text-to-image/domain/constants/index.ts +0 -0
- /package/src/{features → domains}/text-to-image/domain/constants/options.constants.ts +0 -0
- /package/src/{features → domains}/text-to-image/domain/constants/styles.constants.ts +0 -0
- /package/src/{features → domains}/text-to-image/domain/index.ts +0 -0
- /package/src/{features → domains}/text-to-image/domain/types/config.types.ts +0 -0
- /package/src/{features → domains}/text-to-image/domain/types/form.types.ts +0 -0
- /package/src/{features → domains}/text-to-image/domain/types/index.ts +0 -0
- /package/src/{features → domains}/text-to-image/domain/types/text-to-image.types.ts +0 -0
- /package/src/{features → domains}/text-to-image/index.ts +0 -0
- /package/src/{features → domains}/text-to-image/infrastructure/index.ts +0 -0
- /package/src/{features → domains}/text-to-image/infrastructure/services/index.ts +0 -0
- /package/src/{features → domains}/text-to-image/presentation/components/index.ts +0 -0
- /package/src/{features → domains}/text-to-image/presentation/hooks/index.ts +0 -0
- /package/src/{features → domains}/text-to-image/presentation/hooks/useFormState.ts +0 -0
- /package/src/{features → domains}/text-to-image/presentation/hooks/useGeneration.ts +0 -0
- /package/src/{features → domains}/text-to-image/presentation/hooks/useTextToImageForm.ts +0 -0
- /package/src/{features → domains}/text-to-image/presentation/index.ts +0 -0
- /package/src/{features → domains}/text-to-image/presentation/screens/TextToImageWizardFlow.tsx +0 -0
- /package/src/{features → domains}/text-to-image/presentation/screens/TextToImageWizardFlow.types.ts +0 -0
- /package/src/{features → domains}/text-to-video/README.md +0 -0
- /package/src/{features → domains}/text-to-video/domain/index.ts +0 -0
- /package/src/{features → domains}/text-to-video/domain/types/callback.types.ts +0 -0
- /package/src/{features → domains}/text-to-video/domain/types/component.types.ts +0 -0
- /package/src/{features → domains}/text-to-video/domain/types/config.types.ts +0 -0
- /package/src/{features → domains}/text-to-video/domain/types/index.ts +0 -0
- /package/src/{features → domains}/text-to-video/domain/types/request.types.ts +0 -0
- /package/src/{features → domains}/text-to-video/domain/types/state.types.ts +0 -0
- /package/src/{features → domains}/text-to-video/index.ts +0 -0
- /package/src/{features → domains}/text-to-video/infrastructure/index.ts +0 -0
- /package/src/{features → domains}/text-to-video/infrastructure/services/index.ts +0 -0
- /package/src/{features → domains}/text-to-video/infrastructure/services/text-to-video-executor.ts +0 -0
- /package/src/{features → domains}/text-to-video/presentation/components/FrameSelector.tsx +0 -0
- /package/src/{features → domains}/text-to-video/presentation/components/GenerationTabs.tsx +0 -0
- /package/src/{features → domains}/text-to-video/presentation/components/HeroSection.tsx +0 -0
- /package/src/{features → domains}/text-to-video/presentation/components/HintCarousel.tsx +0 -0
- /package/src/{features → domains}/text-to-video/presentation/components/OptionsPanel.tsx +0 -0
- /package/src/{features → domains}/text-to-video/presentation/components/index.ts +0 -0
- /package/src/{features → domains}/text-to-video/presentation/hooks/index.ts +0 -0
- /package/src/{features → domains}/text-to-video/presentation/hooks/useTextToVideoForm.ts +0 -0
- /package/src/{features → domains}/text-to-video/presentation/index.ts +0 -0
- /package/src/{features → domains}/text-to-video/presentation/screens/TextToVideoWizardFlow.tsx +0 -0
|
@@ -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
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
3
|
-
* Orchestrates wizard-based generation
|
|
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
|
|
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
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
+
};
|
|
@@ -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
|
+
}
|