@umituz/react-native-ai-generation-content 1.27.12 → 1.27.14
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/features/image-to-video/presentation/hooks/useImageToVideoFeature.ts +131 -20
- package/src/features/text-to-image/index.ts +0 -7
- package/src/features/text-to-image/presentation/hooks/index.ts +0 -7
- package/src/infrastructure/utils/feature-utils.ts +1 -6
- package/src/domains/generation/wizard/presentation/hooks/useGateStep.ts +0 -138
- package/src/features/image-to-video/presentation/hooks/useGenerationExecution.ts +0 -143
- package/src/features/image-to-video/presentation/hooks/useImageToVideoValidation.ts +0 -46
- package/src/features/text-to-image/presentation/hooks/useTextToImageFeature.ts +0 -111
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.27.
|
|
3
|
+
"version": "1.27.14",
|
|
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,11 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Image-to-Video Feature Hook
|
|
3
|
-
*
|
|
3
|
+
* Uses centralized useGenerationOrchestrator for consistent auth, credits, and error handling
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useState, useCallback, useMemo } from "react";
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
useGenerationOrchestrator,
|
|
9
|
+
type GenerationStrategy,
|
|
10
|
+
type AlertMessages,
|
|
11
|
+
} from "../../../../presentation/hooks/generation";
|
|
12
|
+
import { executeImageToVideo } from "../../infrastructure/services";
|
|
9
13
|
import type {
|
|
10
14
|
ImageToVideoFeatureState,
|
|
11
15
|
ImageToVideoFeatureConfig,
|
|
@@ -16,7 +20,6 @@ import type {
|
|
|
16
20
|
|
|
17
21
|
declare const __DEV__: boolean;
|
|
18
22
|
|
|
19
|
-
// Initial state (inlined from constants file)
|
|
20
23
|
const INITIAL_STATE: ImageToVideoFeatureState = {
|
|
21
24
|
imageUri: null,
|
|
22
25
|
motionPrompt: "",
|
|
@@ -27,7 +30,14 @@ const INITIAL_STATE: ImageToVideoFeatureState = {
|
|
|
27
30
|
error: null,
|
|
28
31
|
};
|
|
29
32
|
|
|
30
|
-
|
|
33
|
+
const DEFAULT_ALERT_MESSAGES: AlertMessages = {
|
|
34
|
+
networkError: "No internet connection. Please check your network.",
|
|
35
|
+
policyViolation: "Content not allowed. Please try again.",
|
|
36
|
+
saveFailed: "Failed to save. Please try again.",
|
|
37
|
+
creditFailed: "Credit operation failed. Please try again.",
|
|
38
|
+
unknown: "An error occurred. Please try again.",
|
|
39
|
+
};
|
|
40
|
+
|
|
31
41
|
export interface UseImageToVideoFeatureProps {
|
|
32
42
|
config: ImageToVideoFeatureConfig;
|
|
33
43
|
callbacks?: ImageToVideoFeatureCallbacks;
|
|
@@ -44,15 +54,99 @@ export interface UseImageToVideoFeatureReturn {
|
|
|
44
54
|
canGenerate: boolean;
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
interface VideoGenerationInput {
|
|
58
|
+
imageUri: string;
|
|
59
|
+
imageBase64: string;
|
|
60
|
+
motionPrompt: string;
|
|
61
|
+
options?: Omit<ImageToVideoGenerateParams, "imageUri" | "motionPrompt">;
|
|
62
|
+
creationId: string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function generateCreationId(): string {
|
|
66
|
+
return `image-to-video_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
67
|
+
}
|
|
68
|
+
|
|
47
69
|
export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseImageToVideoFeatureReturn {
|
|
48
70
|
const { config, callbacks, userId } = props;
|
|
49
71
|
const [state, setState] = useState<ImageToVideoFeatureState>(INITIAL_STATE);
|
|
50
72
|
|
|
51
|
-
const
|
|
73
|
+
const strategy: GenerationStrategy<VideoGenerationInput, ImageToVideoResult> = useMemo(
|
|
74
|
+
() => ({
|
|
75
|
+
execute: async (input, onProgress) => {
|
|
76
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
77
|
+
console.log("[ImageToVideo] Executing generation, creationId:", input.creationId);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
config.onProcessingStart?.();
|
|
81
|
+
|
|
82
|
+
callbacks?.onGenerationStart?.({
|
|
83
|
+
creationId: input.creationId,
|
|
84
|
+
type: "image-to-video",
|
|
85
|
+
imageUri: input.imageUri,
|
|
86
|
+
metadata: input.options as Record<string, unknown> | undefined,
|
|
87
|
+
}).catch(() => {});
|
|
88
|
+
|
|
89
|
+
const result = await executeImageToVideo(
|
|
90
|
+
{
|
|
91
|
+
imageUri: input.imageUri,
|
|
92
|
+
imageBase64: input.imageBase64,
|
|
93
|
+
userId,
|
|
94
|
+
motionPrompt: input.motionPrompt || undefined,
|
|
95
|
+
options: input.options,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
model: config.model,
|
|
99
|
+
buildInput: config.buildInput,
|
|
100
|
+
extractResult: config.extractResult,
|
|
101
|
+
onProgress: (progress) => {
|
|
102
|
+
setState((prev) => ({ ...prev, progress }));
|
|
103
|
+
onProgress?.(progress);
|
|
104
|
+
callbacks?.onProgress?.(progress);
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (!result.success || !result.videoUrl) {
|
|
110
|
+
throw new Error(result.error || "Generation failed");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
setState((prev) => ({
|
|
114
|
+
...prev,
|
|
115
|
+
videoUrl: result.videoUrl ?? null,
|
|
116
|
+
thumbnailUrl: result.thumbnailUrl ?? null,
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
return result;
|
|
120
|
+
},
|
|
121
|
+
getCreditCost: () => config.creditCost ?? 0,
|
|
122
|
+
save: async (result) => {
|
|
123
|
+
if (result.success && result.videoUrl && state.imageUri) {
|
|
124
|
+
await callbacks?.onCreationSave?.({
|
|
125
|
+
creationId: generateCreationId(),
|
|
126
|
+
type: "image-to-video",
|
|
127
|
+
videoUrl: result.videoUrl,
|
|
128
|
+
thumbnailUrl: result.thumbnailUrl,
|
|
129
|
+
imageUri: state.imageUri,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
}),
|
|
134
|
+
[config, callbacks, userId, state.imageUri],
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const orchestrator = useGenerationOrchestrator(strategy, {
|
|
52
138
|
userId,
|
|
53
|
-
|
|
54
|
-
callbacks,
|
|
55
|
-
|
|
139
|
+
alertMessages: DEFAULT_ALERT_MESSAGES,
|
|
140
|
+
onCreditsExhausted: () => callbacks?.onShowPaywall?.(config.creditCost ?? 0),
|
|
141
|
+
onSuccess: (result) => {
|
|
142
|
+
const videoResult = result as ImageToVideoResult;
|
|
143
|
+
callbacks?.onGenerate?.(videoResult);
|
|
144
|
+
config.onProcessingComplete?.(videoResult);
|
|
145
|
+
},
|
|
146
|
+
onError: (err) => {
|
|
147
|
+
callbacks?.onError?.(err.message);
|
|
148
|
+
config.onError?.(err.message);
|
|
149
|
+
},
|
|
56
150
|
});
|
|
57
151
|
|
|
58
152
|
const setImageUri = useCallback(
|
|
@@ -77,23 +171,40 @@ export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseI
|
|
|
77
171
|
console.log("[ImageToVideoFeature] generate called, hasImage:", !!effectiveImageUri);
|
|
78
172
|
}
|
|
79
173
|
|
|
174
|
+
if (!effectiveImageUri) {
|
|
175
|
+
const error = "Image is required";
|
|
176
|
+
setState((prev) => ({ ...prev, error }));
|
|
177
|
+
callbacks?.onError?.(error);
|
|
178
|
+
return { success: false, error };
|
|
179
|
+
}
|
|
180
|
+
|
|
80
181
|
if (paramImageUri) {
|
|
81
182
|
setState((prev) => ({ ...prev, imageUri: paramImageUri }));
|
|
82
183
|
}
|
|
83
184
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
config.
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
185
|
+
setState((prev) => ({ ...prev, isProcessing: true, error: null, progress: 0 }));
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const imageBase64 = await config.prepareImage(effectiveImageUri);
|
|
189
|
+
|
|
190
|
+
const input: VideoGenerationInput = {
|
|
191
|
+
imageUri: effectiveImageUri,
|
|
192
|
+
imageBase64,
|
|
193
|
+
motionPrompt: effectiveMotionPrompt,
|
|
194
|
+
options,
|
|
195
|
+
creationId: generateCreationId(),
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
await orchestrator.generate(input);
|
|
199
|
+
setState((prev) => ({ ...prev, isProcessing: false }));
|
|
200
|
+
return { success: true, videoUrl: state.videoUrl || undefined };
|
|
201
|
+
} catch (error) {
|
|
202
|
+
const message = error instanceof Error ? error.message : "Generation failed";
|
|
203
|
+
setState((prev) => ({ ...prev, isProcessing: false, error: message }));
|
|
204
|
+
return { success: false, error: message };
|
|
92
205
|
}
|
|
93
|
-
|
|
94
|
-
return executeGeneration(effectiveImageUri!, effectiveMotionPrompt, options);
|
|
95
206
|
},
|
|
96
|
-
[state.imageUri, state.motionPrompt,
|
|
207
|
+
[state.imageUri, state.motionPrompt, state.videoUrl, config, callbacks, orchestrator],
|
|
97
208
|
);
|
|
98
209
|
|
|
99
210
|
const reset = useCallback(() => {
|
|
@@ -83,13 +83,6 @@ export type {
|
|
|
83
83
|
UseTextToImageFormReturn,
|
|
84
84
|
} from "./presentation";
|
|
85
85
|
|
|
86
|
-
// Provider-based Feature Hook
|
|
87
|
-
export { useTextToImageFeature } from "./presentation";
|
|
88
|
-
export type {
|
|
89
|
-
UseTextToImageFeatureProps,
|
|
90
|
-
UseTextToImageFeatureReturn,
|
|
91
|
-
} from "./presentation";
|
|
92
|
-
|
|
93
86
|
// =============================================================================
|
|
94
87
|
// PRESENTATION LAYER - Components
|
|
95
88
|
// =============================================================================
|
|
@@ -21,10 +21,3 @@ export type {
|
|
|
21
21
|
UseTextToImageFormOptions,
|
|
22
22
|
UseTextToImageFormReturn,
|
|
23
23
|
} from "./useTextToImageForm";
|
|
24
|
-
|
|
25
|
-
// Provider-based Feature Hook
|
|
26
|
-
export { useTextToImageFeature } from "./useTextToImageFeature";
|
|
27
|
-
export type {
|
|
28
|
-
UseTextToImageFeatureProps,
|
|
29
|
-
UseTextToImageFeatureReturn,
|
|
30
|
-
} from "./useTextToImageFeature";
|
|
@@ -3,14 +3,9 @@
|
|
|
3
3
|
* Uses ONLY configured app services - no alternatives
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import { readFileAsBase64 } from "@umituz/react-native-design-system";
|
|
7
7
|
import { getAuthService, getCreditService, getPaywallService, isAppServicesConfigured } from "../config/app-services.config";
|
|
8
8
|
|
|
9
|
-
async function readFileAsBase64(uri: string): Promise<string> {
|
|
10
|
-
const base64 = await FileSystem.readAsStringAsync(uri, { encoding: "base64" });
|
|
11
|
-
return `data:image/jpeg;base64,${base64}`;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
9
|
declare const __DEV__: boolean;
|
|
15
10
|
|
|
16
11
|
export type ImageSelector = () => Promise<string | null>;
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useGateStep Hook
|
|
3
|
-
* Handles auth and credit gate steps in wizard flow
|
|
4
|
-
*
|
|
5
|
-
* Gates are "invisible" steps that:
|
|
6
|
-
* - Auto-advance when condition is met
|
|
7
|
-
* - Show modal/paywall when blocked
|
|
8
|
-
* - Never show UI themselves
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import { useEffect, useRef, useCallback } from "react";
|
|
12
|
-
import type { GateResult } from "../../../../../domain/entities/flow-config.types";
|
|
13
|
-
|
|
14
|
-
declare const __DEV__: boolean;
|
|
15
|
-
|
|
16
|
-
export interface GateCallbacks {
|
|
17
|
-
/** Called when auth is required - should show auth modal */
|
|
18
|
-
readonly onAuthRequired?: () => void;
|
|
19
|
-
/** Called when credits are exhausted - should show paywall */
|
|
20
|
-
readonly onCreditsExhausted?: () => void;
|
|
21
|
-
/** Called when gate passes - auto advance to next step */
|
|
22
|
-
readonly onGatePassed?: () => void;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface AuthGateState {
|
|
26
|
-
readonly isAuthenticated: boolean;
|
|
27
|
-
readonly isAnonymous: boolean;
|
|
28
|
-
readonly userId: string | null;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface CreditGateState {
|
|
32
|
-
readonly creditBalance: number;
|
|
33
|
-
readonly requiredCredits: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface UseGateStepProps {
|
|
37
|
-
readonly gateType: "auth" | "credit";
|
|
38
|
-
readonly authState?: AuthGateState;
|
|
39
|
-
readonly creditState?: CreditGateState;
|
|
40
|
-
readonly callbacks: GateCallbacks;
|
|
41
|
-
readonly allowAnonymous?: boolean;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export interface UseGateStepReturn {
|
|
45
|
-
readonly gateResult: GateResult;
|
|
46
|
-
readonly checkGate: () => GateResult;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Hook to manage gate step logic
|
|
51
|
-
* Automatically triggers callbacks and advances when conditions are met
|
|
52
|
-
*/
|
|
53
|
-
export function useGateStep(props: UseGateStepProps): UseGateStepReturn {
|
|
54
|
-
const { gateType, authState, creditState, callbacks, allowAnonymous = false } = props;
|
|
55
|
-
|
|
56
|
-
const hasChecked = useRef(false);
|
|
57
|
-
|
|
58
|
-
const checkAuthGate = useCallback((): GateResult => {
|
|
59
|
-
if (!authState) return "pending";
|
|
60
|
-
|
|
61
|
-
const { isAuthenticated, isAnonymous, userId } = authState;
|
|
62
|
-
|
|
63
|
-
// Allow anonymous if configured
|
|
64
|
-
if (allowAnonymous && userId) {
|
|
65
|
-
return "passed";
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Must be authenticated and not anonymous
|
|
69
|
-
if (isAuthenticated && !isAnonymous && userId) {
|
|
70
|
-
return "passed";
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return "blocked";
|
|
74
|
-
}, [authState, allowAnonymous]);
|
|
75
|
-
|
|
76
|
-
const checkCreditGate = useCallback((): GateResult => {
|
|
77
|
-
if (!creditState) return "pending";
|
|
78
|
-
|
|
79
|
-
const { creditBalance, requiredCredits } = creditState;
|
|
80
|
-
|
|
81
|
-
if (creditBalance >= requiredCredits) {
|
|
82
|
-
return "passed";
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return "blocked";
|
|
86
|
-
}, [creditState]);
|
|
87
|
-
|
|
88
|
-
const checkGate = useCallback((): GateResult => {
|
|
89
|
-
if (gateType === "auth") {
|
|
90
|
-
return checkAuthGate();
|
|
91
|
-
}
|
|
92
|
-
return checkCreditGate();
|
|
93
|
-
}, [gateType, checkAuthGate, checkCreditGate]);
|
|
94
|
-
|
|
95
|
-
// Auto-check and trigger callbacks on mount
|
|
96
|
-
useEffect(() => {
|
|
97
|
-
if (hasChecked.current) return;
|
|
98
|
-
|
|
99
|
-
const result = checkGate();
|
|
100
|
-
|
|
101
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
102
|
-
console.log(`[useGateStep] ${gateType} gate check:`, result);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (result === "passed") {
|
|
106
|
-
hasChecked.current = true;
|
|
107
|
-
callbacks.onGatePassed?.();
|
|
108
|
-
} else if (result === "blocked") {
|
|
109
|
-
hasChecked.current = true;
|
|
110
|
-
if (gateType === "auth") {
|
|
111
|
-
callbacks.onAuthRequired?.();
|
|
112
|
-
} else {
|
|
113
|
-
callbacks.onCreditsExhausted?.();
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}, [gateType, checkGate, callbacks]);
|
|
117
|
-
|
|
118
|
-
// Re-check when auth/credit state changes
|
|
119
|
-
useEffect(() => {
|
|
120
|
-
if (!hasChecked.current) return;
|
|
121
|
-
|
|
122
|
-
const result = checkGate();
|
|
123
|
-
|
|
124
|
-
if (result === "passed") {
|
|
125
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
126
|
-
console.log(`[useGateStep] ${gateType} gate now passed, advancing...`);
|
|
127
|
-
}
|
|
128
|
-
callbacks.onGatePassed?.();
|
|
129
|
-
}
|
|
130
|
-
}, [authState, creditState, gateType, checkGate, callbacks]);
|
|
131
|
-
|
|
132
|
-
return {
|
|
133
|
-
gateResult: checkGate(),
|
|
134
|
-
checkGate,
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
export default useGateStep;
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useGenerationExecution Hook
|
|
3
|
-
* Handles the core generation execution logic for image-to-video
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useCallback } from "react";
|
|
7
|
-
import { executeImageToVideo } from "../../infrastructure/services";
|
|
8
|
-
import type {
|
|
9
|
-
ImageToVideoFeatureConfig,
|
|
10
|
-
ImageToVideoFeatureCallbacks,
|
|
11
|
-
ImageToVideoResult,
|
|
12
|
-
ImageToVideoGenerateParams,
|
|
13
|
-
ImageToVideoFeatureState,
|
|
14
|
-
} from "../../domain/types";
|
|
15
|
-
|
|
16
|
-
declare const __DEV__: boolean;
|
|
17
|
-
|
|
18
|
-
interface UseGenerationExecutionParams {
|
|
19
|
-
userId: string;
|
|
20
|
-
config: ImageToVideoFeatureConfig;
|
|
21
|
-
callbacks?: ImageToVideoFeatureCallbacks;
|
|
22
|
-
setState: React.Dispatch<React.SetStateAction<ImageToVideoFeatureState>>;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function useGenerationExecution({
|
|
26
|
-
userId,
|
|
27
|
-
config,
|
|
28
|
-
callbacks,
|
|
29
|
-
setState,
|
|
30
|
-
}: UseGenerationExecutionParams) {
|
|
31
|
-
return useCallback(
|
|
32
|
-
async (
|
|
33
|
-
imageUri: string,
|
|
34
|
-
motionPrompt: string,
|
|
35
|
-
options?: Omit<ImageToVideoGenerateParams, "imageUri" | "motionPrompt">,
|
|
36
|
-
): Promise<ImageToVideoResult> => {
|
|
37
|
-
const creationId = `image-to-video_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
38
|
-
|
|
39
|
-
setState((prev) => ({
|
|
40
|
-
...prev,
|
|
41
|
-
imageUri,
|
|
42
|
-
isProcessing: true,
|
|
43
|
-
progress: 0,
|
|
44
|
-
error: null,
|
|
45
|
-
}));
|
|
46
|
-
|
|
47
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
48
|
-
console.log("[ImageToVideoFeature] Starting generation, creationId:", creationId);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
config.onProcessingStart?.();
|
|
52
|
-
|
|
53
|
-
if (callbacks?.onGenerationStart) {
|
|
54
|
-
callbacks.onGenerationStart({
|
|
55
|
-
creationId,
|
|
56
|
-
type: "image-to-video",
|
|
57
|
-
imageUri,
|
|
58
|
-
metadata: options as Record<string, unknown> | undefined,
|
|
59
|
-
}).catch((err) => {
|
|
60
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
61
|
-
console.warn("[ImageToVideoFeature] onGenerationStart failed:", err);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
const imageBase64 = await config.prepareImage(imageUri);
|
|
68
|
-
|
|
69
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
70
|
-
console.log("[ImageToVideoFeature] Image prepared, calling executeImageToVideo");
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
const result = await executeImageToVideo(
|
|
74
|
-
{
|
|
75
|
-
imageUri,
|
|
76
|
-
imageBase64,
|
|
77
|
-
userId,
|
|
78
|
-
motionPrompt: motionPrompt || undefined,
|
|
79
|
-
options,
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
model: config.model,
|
|
83
|
-
buildInput: config.buildInput,
|
|
84
|
-
extractResult: config.extractResult,
|
|
85
|
-
onProgress: (progress) => {
|
|
86
|
-
setState((prev) => ({ ...prev, progress }));
|
|
87
|
-
callbacks?.onProgress?.(progress);
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
if (result.success && result.videoUrl) {
|
|
93
|
-
setState((prev) => ({
|
|
94
|
-
...prev,
|
|
95
|
-
videoUrl: result.videoUrl ?? null,
|
|
96
|
-
thumbnailUrl: result.thumbnailUrl ?? null,
|
|
97
|
-
isProcessing: false,
|
|
98
|
-
progress: 100,
|
|
99
|
-
}));
|
|
100
|
-
|
|
101
|
-
if (callbacks?.onCreditDeduct && config.creditCost) {
|
|
102
|
-
await callbacks.onCreditDeduct(config.creditCost);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
if (callbacks?.onCreationSave) {
|
|
106
|
-
await callbacks.onCreationSave({
|
|
107
|
-
creationId,
|
|
108
|
-
type: "image-to-video",
|
|
109
|
-
videoUrl: result.videoUrl,
|
|
110
|
-
thumbnailUrl: result.thumbnailUrl,
|
|
111
|
-
imageUri,
|
|
112
|
-
metadata: options as Record<string, unknown> | undefined,
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
callbacks?.onGenerate?.(result);
|
|
117
|
-
} else {
|
|
118
|
-
const error = result.error || "Generation failed";
|
|
119
|
-
setState((prev) => ({ ...prev, isProcessing: false, error }));
|
|
120
|
-
config.onError?.(error);
|
|
121
|
-
callbacks?.onError?.(error);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
config.onProcessingComplete?.(result);
|
|
125
|
-
return result;
|
|
126
|
-
} catch (err) {
|
|
127
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
128
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
129
|
-
console.error("[ImageToVideoFeature] Generation error:", errorMessage);
|
|
130
|
-
}
|
|
131
|
-
setState((prev) => ({
|
|
132
|
-
...prev,
|
|
133
|
-
isProcessing: false,
|
|
134
|
-
error: errorMessage,
|
|
135
|
-
}));
|
|
136
|
-
config.onError?.(errorMessage);
|
|
137
|
-
callbacks?.onError?.(errorMessage);
|
|
138
|
-
return { success: false, error: errorMessage };
|
|
139
|
-
}
|
|
140
|
-
},
|
|
141
|
-
[userId, config, callbacks],
|
|
142
|
-
);
|
|
143
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Image-to-Video Validation Utilities
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { ImageToVideoFeatureCallbacks, ImageToVideoResult } from "../../domain/types";
|
|
6
|
-
|
|
7
|
-
declare const __DEV__: boolean;
|
|
8
|
-
|
|
9
|
-
export interface ValidationResult extends ImageToVideoResult {
|
|
10
|
-
shouldProceed: boolean;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function validateImageToVideoGeneration(
|
|
14
|
-
effectiveImageUri: string | null,
|
|
15
|
-
callbacks?: ImageToVideoFeatureCallbacks,
|
|
16
|
-
creditCost?: number,
|
|
17
|
-
): Promise<ValidationResult> {
|
|
18
|
-
if (!effectiveImageUri) {
|
|
19
|
-
const error = "Image is required";
|
|
20
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
21
|
-
console.log("[ImageToVideoFeature] Generate failed: Image is required");
|
|
22
|
-
}
|
|
23
|
-
callbacks?.onError?.(error);
|
|
24
|
-
return { success: false, error, shouldProceed: false };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
if (callbacks?.onAuthCheck && !callbacks.onAuthCheck()) {
|
|
28
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
29
|
-
console.log("[ImageToVideoFeature] Generate failed: Authentication required");
|
|
30
|
-
}
|
|
31
|
-
return { success: false, error: "Authentication required", shouldProceed: false };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (callbacks?.onCreditCheck && creditCost) {
|
|
35
|
-
const hasCredits = await callbacks.onCreditCheck(creditCost);
|
|
36
|
-
if (!hasCredits) {
|
|
37
|
-
callbacks?.onShowPaywall?.(creditCost);
|
|
38
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
39
|
-
console.log("[ImageToVideoFeature] Generate failed: Insufficient credits");
|
|
40
|
-
}
|
|
41
|
-
return { success: false, error: "Insufficient credits", shouldProceed: false };
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
return { success: true, shouldProceed: true };
|
|
46
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Text-to-Image Feature Hook
|
|
3
|
-
* Provider-agnostic hook for text-to-image generation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { useState, useCallback } from "react";
|
|
7
|
-
import { executeTextToImage } from "../../infrastructure/services";
|
|
8
|
-
import type {
|
|
9
|
-
TextToImageFeatureState,
|
|
10
|
-
TextToImageFeatureConfig,
|
|
11
|
-
TextToImageResult,
|
|
12
|
-
TextToImageOptions,
|
|
13
|
-
} from "../../domain/types";
|
|
14
|
-
|
|
15
|
-
export interface UseTextToImageFeatureProps {
|
|
16
|
-
config: TextToImageFeatureConfig;
|
|
17
|
-
userId: string;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface UseTextToImageFeatureReturn {
|
|
21
|
-
state: TextToImageFeatureState;
|
|
22
|
-
setPrompt: (prompt: string) => void;
|
|
23
|
-
generate: (options?: TextToImageOptions) => Promise<TextToImageResult>;
|
|
24
|
-
reset: () => void;
|
|
25
|
-
isReady: boolean;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const initialState: TextToImageFeatureState = {
|
|
29
|
-
prompt: "",
|
|
30
|
-
imageUrl: null,
|
|
31
|
-
imageUrls: [],
|
|
32
|
-
isProcessing: false,
|
|
33
|
-
progress: 0,
|
|
34
|
-
error: null,
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export function useTextToImageFeature(
|
|
38
|
-
props: UseTextToImageFeatureProps,
|
|
39
|
-
): UseTextToImageFeatureReturn {
|
|
40
|
-
const { config, userId } = props;
|
|
41
|
-
const [state, setState] = useState<TextToImageFeatureState>(initialState);
|
|
42
|
-
|
|
43
|
-
const setPrompt = useCallback(
|
|
44
|
-
(prompt: string) => {
|
|
45
|
-
setState((prev) => ({ ...prev, prompt, error: null }));
|
|
46
|
-
config.onPromptChange?.(prompt);
|
|
47
|
-
},
|
|
48
|
-
[config],
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
const generate = useCallback(
|
|
52
|
-
async (options?: TextToImageOptions): Promise<TextToImageResult> => {
|
|
53
|
-
if (!state.prompt) {
|
|
54
|
-
const error = "Prompt is required";
|
|
55
|
-
setState((prev) => ({ ...prev, error }));
|
|
56
|
-
return { success: false, error };
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
setState((prev) => ({
|
|
60
|
-
...prev,
|
|
61
|
-
isProcessing: true,
|
|
62
|
-
progress: 0,
|
|
63
|
-
error: null,
|
|
64
|
-
}));
|
|
65
|
-
|
|
66
|
-
config.onProcessingStart?.();
|
|
67
|
-
|
|
68
|
-
const result = await executeTextToImage(
|
|
69
|
-
{ prompt: state.prompt, userId, options },
|
|
70
|
-
{
|
|
71
|
-
model: config.model,
|
|
72
|
-
buildInput: config.buildInput,
|
|
73
|
-
extractResult: config.extractResult,
|
|
74
|
-
onProgress: (progress) => {
|
|
75
|
-
setState((prev) => ({ ...prev, progress }));
|
|
76
|
-
},
|
|
77
|
-
},
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
if (result.success && result.imageUrl) {
|
|
81
|
-
setState((prev) => ({
|
|
82
|
-
...prev,
|
|
83
|
-
imageUrl: result.imageUrl ?? null,
|
|
84
|
-
imageUrls: result.imageUrls ?? [],
|
|
85
|
-
isProcessing: false,
|
|
86
|
-
progress: 100,
|
|
87
|
-
}));
|
|
88
|
-
} else {
|
|
89
|
-
const error = result.error || "Generation failed";
|
|
90
|
-
setState((prev) => ({
|
|
91
|
-
...prev,
|
|
92
|
-
isProcessing: false,
|
|
93
|
-
error,
|
|
94
|
-
}));
|
|
95
|
-
config.onError?.(error);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
config.onProcessingComplete?.(result);
|
|
99
|
-
return result;
|
|
100
|
-
},
|
|
101
|
-
[state.prompt, userId, config],
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
const reset = useCallback(() => {
|
|
105
|
-
setState(initialState);
|
|
106
|
-
}, []);
|
|
107
|
-
|
|
108
|
-
const isReady = state.prompt.length > 0 && !state.isProcessing;
|
|
109
|
-
|
|
110
|
-
return { state, setPrompt, generate, reset, isReady };
|
|
111
|
-
}
|