@umituz/react-native-ai-generation-content 1.19.9 → 1.20.2
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 +37 -27
- package/src/features/text-to-image/presentation/hooks/useGeneration.ts +83 -103
- package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +138 -66
- package/src/presentation/hooks/generation/index.ts +4 -0
- package/src/presentation/hooks/generation/orchestrator.ts +159 -89
- package/src/presentation/hooks/generation/types.ts +45 -0
- package/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.constants.ts +0 -15
- package/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.types.ts +0 -27
- package/src/features/text-to-video/presentation/hooks/textToVideoExecution.ts +0 -134
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.2",
|
|
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",
|
|
@@ -6,24 +6,47 @@
|
|
|
6
6
|
import { useState, useCallback, useMemo } from "react";
|
|
7
7
|
import { useGenerationExecution } from "./useGenerationExecution";
|
|
8
8
|
import { validateImageToVideoGeneration } from "./useImageToVideoValidation";
|
|
9
|
-
import { INITIAL_IMAGE_TO_VIDEO_STATE } from "./useImageToVideoFeature.constants";
|
|
10
|
-
import type {
|
|
11
|
-
UseImageToVideoFeatureProps,
|
|
12
|
-
UseImageToVideoFeatureReturn,
|
|
13
|
-
} from "./useImageToVideoFeature.types";
|
|
14
9
|
import type {
|
|
15
10
|
ImageToVideoFeatureState,
|
|
16
|
-
|
|
11
|
+
ImageToVideoFeatureConfig,
|
|
17
12
|
ImageToVideoResult,
|
|
13
|
+
ImageToVideoFeatureCallbacks,
|
|
14
|
+
ImageToVideoGenerateParams,
|
|
18
15
|
} from "../../domain/types";
|
|
19
16
|
|
|
20
17
|
declare const __DEV__: boolean;
|
|
21
18
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
// Initial state (inlined from constants file)
|
|
20
|
+
const INITIAL_STATE: ImageToVideoFeatureState = {
|
|
21
|
+
imageUri: null,
|
|
22
|
+
motionPrompt: "",
|
|
23
|
+
videoUrl: null,
|
|
24
|
+
thumbnailUrl: null,
|
|
25
|
+
isProcessing: false,
|
|
26
|
+
progress: 0,
|
|
27
|
+
error: null,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
// Types (inlined from types file)
|
|
31
|
+
export interface UseImageToVideoFeatureProps {
|
|
32
|
+
config: ImageToVideoFeatureConfig;
|
|
33
|
+
callbacks?: ImageToVideoFeatureCallbacks;
|
|
34
|
+
userId: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface UseImageToVideoFeatureReturn {
|
|
38
|
+
state: ImageToVideoFeatureState;
|
|
39
|
+
setImageUri: (uri: string) => void;
|
|
40
|
+
setMotionPrompt: (prompt: string) => void;
|
|
41
|
+
generate: (params?: ImageToVideoGenerateParams) => Promise<ImageToVideoResult>;
|
|
42
|
+
reset: () => void;
|
|
43
|
+
isReady: boolean;
|
|
44
|
+
canGenerate: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseImageToVideoFeatureReturn {
|
|
25
48
|
const { config, callbacks, userId } = props;
|
|
26
|
-
const [state, setState] = useState<ImageToVideoFeatureState>(
|
|
49
|
+
const [state, setState] = useState<ImageToVideoFeatureState>(INITIAL_STATE);
|
|
27
50
|
|
|
28
51
|
const executeGeneration = useGenerationExecution({
|
|
29
52
|
userId,
|
|
@@ -68,30 +91,17 @@ export function useImageToVideoFeature(
|
|
|
68
91
|
return validation;
|
|
69
92
|
}
|
|
70
93
|
|
|
71
|
-
return executeGeneration(
|
|
72
|
-
effectiveImageUri!,
|
|
73
|
-
effectiveMotionPrompt,
|
|
74
|
-
options
|
|
75
|
-
);
|
|
94
|
+
return executeGeneration(effectiveImageUri!, effectiveMotionPrompt, options);
|
|
76
95
|
},
|
|
77
96
|
[state.imageUri, state.motionPrompt, callbacks, config.creditCost, executeGeneration],
|
|
78
97
|
);
|
|
79
98
|
|
|
80
99
|
const reset = useCallback(() => {
|
|
81
|
-
setState(
|
|
100
|
+
setState(INITIAL_STATE);
|
|
82
101
|
}, []);
|
|
83
102
|
|
|
84
|
-
const isReady = useMemo(
|
|
85
|
-
|
|
86
|
-
[state.imageUri, state.isProcessing],
|
|
87
|
-
);
|
|
88
|
-
|
|
89
|
-
const canGenerate = useMemo(
|
|
90
|
-
() => isReady && !state.error,
|
|
91
|
-
[isReady, state.error],
|
|
92
|
-
);
|
|
103
|
+
const isReady = useMemo(() => state.imageUri !== null && !state.isProcessing, [state.imageUri, state.isProcessing]);
|
|
104
|
+
const canGenerate = useMemo(() => isReady && !state.error, [isReady, state.error]);
|
|
93
105
|
|
|
94
106
|
return { state, setImageUri, setMotionPrompt, generate, reset, isReady, canGenerate };
|
|
95
107
|
}
|
|
96
|
-
|
|
97
|
-
export type { UseImageToVideoFeatureProps, UseImageToVideoFeatureReturn } from "./useImageToVideoFeature.types";
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Text-to-Image Generation Hook
|
|
3
|
-
*
|
|
3
|
+
* Uses centralized orchestrator for auth, credits, and error handling
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import { useCallback, useMemo } from "react";
|
|
7
|
+
import {
|
|
8
|
+
useGenerationOrchestrator,
|
|
9
|
+
type GenerationStrategy,
|
|
10
|
+
type AlertMessages,
|
|
11
|
+
} from "../../../../presentation/hooks/generation";
|
|
7
12
|
import type {
|
|
8
13
|
TextToImageFormState,
|
|
9
14
|
TextToImageCallbacks,
|
|
@@ -11,91 +16,95 @@ import type {
|
|
|
11
16
|
TextToImageGenerationRequest,
|
|
12
17
|
} from "../../domain/types";
|
|
13
18
|
|
|
14
|
-
|
|
15
|
-
isGenerating: boolean;
|
|
16
|
-
progress: number;
|
|
17
|
-
error: string | null;
|
|
18
|
-
}
|
|
19
|
+
declare const __DEV__: boolean;
|
|
19
20
|
|
|
20
21
|
export interface UseGenerationOptions {
|
|
21
22
|
formState: TextToImageFormState;
|
|
22
23
|
callbacks: TextToImageCallbacks;
|
|
24
|
+
userId?: string;
|
|
23
25
|
onPromptCleared?: () => void;
|
|
24
26
|
}
|
|
25
27
|
|
|
28
|
+
export interface GenerationState {
|
|
29
|
+
isGenerating: boolean;
|
|
30
|
+
progress: number;
|
|
31
|
+
error: string | null;
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
export interface UseGenerationReturn {
|
|
27
35
|
generationState: GenerationState;
|
|
28
36
|
totalCost: number;
|
|
29
37
|
handleGenerate: () => Promise<TextToImageGenerationResult | null>;
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
const DEFAULT_ALERT_MESSAGES: AlertMessages = {
|
|
41
|
+
networkError: "No internet connection. Please check your network.",
|
|
42
|
+
policyViolation: "Content not allowed. Please try again.",
|
|
43
|
+
saveFailed: "Failed to save. Please try again.",
|
|
44
|
+
creditFailed: "Credit operation failed. Please try again.",
|
|
45
|
+
unknown: "An error occurred. Please try again.",
|
|
46
|
+
authRequired: "Please sign in to continue.",
|
|
36
47
|
};
|
|
37
48
|
|
|
38
|
-
declare const __DEV__: boolean;
|
|
39
|
-
|
|
40
49
|
export function useGeneration(options: UseGenerationOptions): UseGenerationReturn {
|
|
41
|
-
const { formState, callbacks, onPromptCleared } = options;
|
|
42
|
-
const [generationState, setGenerationState] = useState<GenerationState>(initialState);
|
|
50
|
+
const { formState, callbacks, userId, onPromptCleared } = options;
|
|
43
51
|
|
|
44
52
|
const totalCost = callbacks.calculateCost(formState.numImages, formState.selectedModel);
|
|
45
53
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
// Build strategy for orchestrator
|
|
55
|
+
const strategy: GenerationStrategy<TextToImageGenerationRequest, string[]> = useMemo(
|
|
56
|
+
() => ({
|
|
57
|
+
execute: async (request) => {
|
|
58
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
59
|
+
console.log("[TextToImage] Executing generation:", JSON.stringify(request, null, 2));
|
|
60
|
+
}
|
|
61
|
+
const result = await callbacks.executeGeneration(request);
|
|
62
|
+
if (result.success === false) {
|
|
63
|
+
throw new Error(result.error);
|
|
64
|
+
}
|
|
65
|
+
return result.imageUrls;
|
|
66
|
+
},
|
|
67
|
+
getCreditCost: () => totalCost,
|
|
68
|
+
}),
|
|
69
|
+
[callbacks, totalCost],
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// Use orchestrator with auth support
|
|
73
|
+
const { generate, isGenerating, progress, error } = useGenerationOrchestrator(strategy, {
|
|
74
|
+
userId,
|
|
75
|
+
alertMessages: DEFAULT_ALERT_MESSAGES,
|
|
76
|
+
onCreditsExhausted: () => callbacks.onCreditsRequired?.(totalCost),
|
|
77
|
+
auth: {
|
|
78
|
+
isAuthenticated: callbacks.isAuthenticated,
|
|
79
|
+
onAuthRequired: callbacks.onAuthRequired,
|
|
80
|
+
},
|
|
81
|
+
onSuccess: (result) => {
|
|
82
|
+
const imageUrls = result as string[];
|
|
55
83
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
56
|
-
|
|
57
|
-
console.log("[TextToImage] No prompt provided");
|
|
84
|
+
console.log("[TextToImage] Success! Generated", imageUrls.length, "image(s)");
|
|
58
85
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const isAuth = callbacks.isAuthenticated();
|
|
64
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
65
|
-
|
|
66
|
-
console.log("[TextToImage] isAuthenticated:", isAuth);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
if (!isAuth) {
|
|
86
|
+
callbacks.onSuccess?.(imageUrls);
|
|
87
|
+
onPromptCleared?.();
|
|
88
|
+
},
|
|
89
|
+
onError: (err) => {
|
|
70
90
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
71
|
-
|
|
72
|
-
console.log("[TextToImage] Auth required - calling onAuthRequired");
|
|
91
|
+
console.log("[TextToImage] Error:", err.message);
|
|
73
92
|
}
|
|
74
|
-
callbacks.
|
|
75
|
-
|
|
76
|
-
|
|
93
|
+
callbacks.onError?.(err.message);
|
|
94
|
+
},
|
|
95
|
+
});
|
|
77
96
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
console.log("[TextToImage] canAfford:", affordable, "totalCost:", totalCost);
|
|
82
|
-
}
|
|
97
|
+
const handleGenerate = useCallback(async (): Promise<TextToImageGenerationResult | null> => {
|
|
98
|
+
const trimmedPrompt = formState.prompt.trim();
|
|
83
99
|
|
|
84
|
-
if (!
|
|
100
|
+
if (!trimmedPrompt) {
|
|
85
101
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
86
|
-
|
|
87
|
-
console.log("[TextToImage] Credits required - calling onCreditsRequired");
|
|
102
|
+
console.log("[TextToImage] No prompt provided");
|
|
88
103
|
}
|
|
89
|
-
callbacks.
|
|
90
|
-
return
|
|
104
|
+
callbacks.onError?.("Prompt is required");
|
|
105
|
+
return { success: false, error: "Prompt is required" };
|
|
91
106
|
}
|
|
92
107
|
|
|
93
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
94
|
-
|
|
95
|
-
console.log("[TextToImage] Starting generation...");
|
|
96
|
-
}
|
|
97
|
-
setGenerationState({ isGenerating: true, progress: 0, error: null });
|
|
98
|
-
|
|
99
108
|
const request: TextToImageGenerationRequest = {
|
|
100
109
|
prompt: trimmedPrompt,
|
|
101
110
|
model: formState.selectedModel ?? undefined,
|
|
@@ -109,51 +118,22 @@ export function useGeneration(options: UseGenerationOptions): UseGenerationRetur
|
|
|
109
118
|
};
|
|
110
119
|
|
|
111
120
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
112
|
-
|
|
113
|
-
console.log("[TextToImage] Request:", JSON.stringify(request, null, 2));
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
try {
|
|
117
|
-
const result = await callbacks.executeGeneration(request);
|
|
118
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
119
|
-
const logResult = {
|
|
120
|
-
success: result.success,
|
|
121
|
-
imageCount: result.success ? result.imageUrls?.length : 0,
|
|
122
|
-
error: result.success === false ? result.error : undefined,
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
console.log("[TextToImage] Result:", JSON.stringify(logResult));
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (result.success === true) {
|
|
129
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
130
|
-
|
|
131
|
-
console.log("[TextToImage] Success! Generated", result.imageUrls?.length, "image(s)");
|
|
132
|
-
}
|
|
133
|
-
callbacks.onSuccess?.(result.imageUrls);
|
|
134
|
-
onPromptCleared?.();
|
|
135
|
-
setGenerationState({ isGenerating: false, progress: 100, error: null });
|
|
136
|
-
} else {
|
|
137
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
138
|
-
|
|
139
|
-
console.log("[TextToImage] Generation failed:", result.error);
|
|
140
|
-
}
|
|
141
|
-
setGenerationState({ isGenerating: false, progress: 0, error: result.error });
|
|
142
|
-
callbacks.onError?.(result.error);
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return result;
|
|
146
|
-
} catch (error) {
|
|
147
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
149
|
-
|
|
150
|
-
console.error("[TextToImage] Exception:", message);
|
|
151
|
-
}
|
|
152
|
-
setGenerationState({ isGenerating: false, progress: 0, error: message });
|
|
153
|
-
callbacks.onError?.(message);
|
|
154
|
-
return null;
|
|
121
|
+
console.log("[TextToImage] Starting generation...");
|
|
155
122
|
}
|
|
156
|
-
}, [formState, callbacks, totalCost, onPromptCleared]);
|
|
157
123
|
|
|
158
|
-
|
|
124
|
+
await generate(request);
|
|
125
|
+
|
|
126
|
+
// Return result based on orchestrator state
|
|
127
|
+
return null; // Result handled via callbacks
|
|
128
|
+
}, [formState, generate, callbacks]);
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
generationState: {
|
|
132
|
+
isGenerating,
|
|
133
|
+
progress,
|
|
134
|
+
error: error?.message || null,
|
|
135
|
+
},
|
|
136
|
+
totalCost,
|
|
137
|
+
handleGenerate,
|
|
138
|
+
};
|
|
159
139
|
}
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* useTextToVideoFeature Hook
|
|
3
|
-
*
|
|
3
|
+
* Uses centralized orchestrator for auth, credits, moderation, and error handling
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useState, useCallback, useMemo } from "react";
|
|
7
|
+
import {
|
|
8
|
+
useGenerationOrchestrator,
|
|
9
|
+
type GenerationStrategy,
|
|
10
|
+
type AlertMessages,
|
|
11
|
+
} from "../../../../presentation/hooks/generation";
|
|
12
|
+
import { executeTextToVideo } from "../../infrastructure/services";
|
|
7
13
|
import type {
|
|
8
14
|
TextToVideoFeatureState,
|
|
9
15
|
TextToVideoConfig,
|
|
@@ -13,7 +19,6 @@ import type {
|
|
|
13
19
|
TextToVideoInputBuilder,
|
|
14
20
|
TextToVideoResultExtractor,
|
|
15
21
|
} from "../../domain/types";
|
|
16
|
-
import { executeVideoGeneration } from "./textToVideoExecution";
|
|
17
22
|
|
|
18
23
|
declare const __DEV__: boolean;
|
|
19
24
|
|
|
@@ -47,12 +52,126 @@ const INITIAL_STATE: TextToVideoFeatureState = {
|
|
|
47
52
|
error: null,
|
|
48
53
|
};
|
|
49
54
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
55
|
+
const DEFAULT_ALERT_MESSAGES: AlertMessages = {
|
|
56
|
+
networkError: "No internet connection. Please check your network.",
|
|
57
|
+
policyViolation: "Content not allowed. Please try again.",
|
|
58
|
+
saveFailed: "Failed to save. Please try again.",
|
|
59
|
+
creditFailed: "Credit operation failed. Please try again.",
|
|
60
|
+
unknown: "An error occurred. Please try again.",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
interface VideoGenerationInput {
|
|
64
|
+
prompt: string;
|
|
65
|
+
options?: TextToVideoOptions;
|
|
66
|
+
creationId: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function generateCreationId(): string {
|
|
70
|
+
return `text-to-video_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function useTextToVideoFeature(props: UseTextToVideoFeatureProps): UseTextToVideoFeatureReturn {
|
|
53
74
|
const { config, callbacks, userId, buildInput, extractResult } = props;
|
|
54
75
|
const [state, setState] = useState<TextToVideoFeatureState>(INITIAL_STATE);
|
|
55
76
|
|
|
77
|
+
// Strategy for orchestrator
|
|
78
|
+
const strategy: GenerationStrategy<VideoGenerationInput, TextToVideoResult> = useMemo(
|
|
79
|
+
() => ({
|
|
80
|
+
execute: async (input, onProgress) => {
|
|
81
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
82
|
+
console.log("[TextToVideo] Executing generation:", input.prompt.slice(0, 100));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Notify generation start
|
|
86
|
+
callbacks.onGenerationStart?.({
|
|
87
|
+
creationId: input.creationId,
|
|
88
|
+
type: "text-to-video",
|
|
89
|
+
prompt: input.prompt,
|
|
90
|
+
metadata: input.options as Record<string, unknown> | undefined,
|
|
91
|
+
}).catch(() => {});
|
|
92
|
+
|
|
93
|
+
const result = await executeTextToVideo(
|
|
94
|
+
{ prompt: input.prompt, userId, options: input.options },
|
|
95
|
+
{
|
|
96
|
+
model: config.model,
|
|
97
|
+
buildInput,
|
|
98
|
+
extractResult,
|
|
99
|
+
onProgress: (progress) => {
|
|
100
|
+
setState((prev) => ({ ...prev, progress }));
|
|
101
|
+
onProgress?.(progress);
|
|
102
|
+
callbacks.onProgress?.(progress);
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
if (!result.success || !result.videoUrl) {
|
|
108
|
+
throw new Error(result.error || "Generation failed");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Update state with result
|
|
112
|
+
setState((prev) => ({
|
|
113
|
+
...prev,
|
|
114
|
+
videoUrl: result.videoUrl ?? null,
|
|
115
|
+
thumbnailUrl: result.thumbnailUrl ?? null,
|
|
116
|
+
}));
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
},
|
|
120
|
+
getCreditCost: () => config.creditCost,
|
|
121
|
+
save: async (result, uid) => {
|
|
122
|
+
if (result.success && result.videoUrl) {
|
|
123
|
+
await callbacks.onCreationSave?.({
|
|
124
|
+
creationId: generateCreationId(),
|
|
125
|
+
type: "text-to-video",
|
|
126
|
+
videoUrl: result.videoUrl,
|
|
127
|
+
thumbnailUrl: result.thumbnailUrl,
|
|
128
|
+
prompt: state.prompt,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
}),
|
|
133
|
+
[config, callbacks, buildInput, extractResult, userId, state.prompt],
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
// Use orchestrator with optional callbacks for credits
|
|
137
|
+
const orchestrator = useGenerationOrchestrator(strategy, {
|
|
138
|
+
userId,
|
|
139
|
+
alertMessages: DEFAULT_ALERT_MESSAGES,
|
|
140
|
+
auth: callbacks.onAuthCheck
|
|
141
|
+
? { isAuthenticated: callbacks.onAuthCheck, onAuthRequired: () => {} }
|
|
142
|
+
: undefined,
|
|
143
|
+
credits: callbacks.onCreditCheck
|
|
144
|
+
? {
|
|
145
|
+
checkCredits: async (cost) => (await callbacks.onCreditCheck?.(cost)) ?? true,
|
|
146
|
+
deductCredits: async (cost) => { await callbacks.onCreditDeduct?.(cost); },
|
|
147
|
+
onCreditsExhausted: () => callbacks.onShowPaywall?.(config.creditCost),
|
|
148
|
+
}
|
|
149
|
+
: undefined,
|
|
150
|
+
moderation: callbacks.onModeration
|
|
151
|
+
? {
|
|
152
|
+
checkContent: async (input) => {
|
|
153
|
+
const result = await callbacks.onModeration!((input as VideoGenerationInput).prompt);
|
|
154
|
+
return result;
|
|
155
|
+
},
|
|
156
|
+
onShowWarning: callbacks.onShowModerationWarning,
|
|
157
|
+
}
|
|
158
|
+
: undefined,
|
|
159
|
+
onSuccess: (result) => {
|
|
160
|
+
const videoResult = result as TextToVideoResult;
|
|
161
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
162
|
+
console.log("[TextToVideo] Success!", videoResult.videoUrl?.slice(0, 50));
|
|
163
|
+
}
|
|
164
|
+
callbacks.onGenerate?.(videoResult);
|
|
165
|
+
},
|
|
166
|
+
onError: (err) => {
|
|
167
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
168
|
+
console.log("[TextToVideo] Error:", err.message);
|
|
169
|
+
}
|
|
170
|
+
setState((prev) => ({ ...prev, error: err.message }));
|
|
171
|
+
callbacks.onError?.(err.message);
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
|
|
56
175
|
const setPrompt = useCallback(
|
|
57
176
|
(prompt: string) => {
|
|
58
177
|
setState((prev) => ({ ...prev, prompt, error: null }));
|
|
@@ -70,9 +189,6 @@ export function useTextToVideoFeature(
|
|
|
70
189
|
const error = "Prompt is required";
|
|
71
190
|
setState((prev) => ({ ...prev, error }));
|
|
72
191
|
callbacks.onError?.(error);
|
|
73
|
-
if (__DEV__) {
|
|
74
|
-
console.log("[TextToVideoFeature] Generate failed: Prompt is required");
|
|
75
|
-
}
|
|
76
192
|
return { success: false, error };
|
|
77
193
|
}
|
|
78
194
|
|
|
@@ -80,74 +196,30 @@ export function useTextToVideoFeature(
|
|
|
80
196
|
setState((prev) => ({ ...prev, prompt: effectivePrompt }));
|
|
81
197
|
}
|
|
82
198
|
|
|
83
|
-
|
|
84
|
-
if (__DEV__) {
|
|
85
|
-
console.log("[TextToVideoFeature] Generate failed: Authentication required");
|
|
86
|
-
}
|
|
87
|
-
return { success: false, error: "Authentication required" };
|
|
88
|
-
}
|
|
199
|
+
setState((prev) => ({ ...prev, isProcessing: true, progress: 0, error: null }));
|
|
89
200
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
!(await callbacks.onCreditCheck(config.creditCost))
|
|
93
|
-
) {
|
|
94
|
-
callbacks.onShowPaywall?.(config.creditCost);
|
|
95
|
-
if (__DEV__) {
|
|
96
|
-
console.log("[TextToVideoFeature] Generate failed: Insufficient credits");
|
|
97
|
-
}
|
|
98
|
-
return { success: false, error: "Insufficient credits" };
|
|
99
|
-
}
|
|
201
|
+
const creationId = generateCreationId();
|
|
202
|
+
await orchestrator.generate({ prompt: effectivePrompt, options, creationId });
|
|
100
203
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
moderationResult.warnings,
|
|
107
|
-
() => {
|
|
108
|
-
setState((prev) => ({ ...prev, isProcessing: false }));
|
|
109
|
-
resolve({ success: false, error: "Content policy violation" });
|
|
110
|
-
},
|
|
111
|
-
async () => {
|
|
112
|
-
const result = await executeVideoGeneration(
|
|
113
|
-
effectivePrompt,
|
|
114
|
-
options,
|
|
115
|
-
{ userId, config, callbacks, buildInput, extractResult },
|
|
116
|
-
{
|
|
117
|
-
onStateUpdate: (update) =>
|
|
118
|
-
setState((prev) => ({ ...prev, ...update })),
|
|
119
|
-
},
|
|
120
|
-
);
|
|
121
|
-
resolve(result);
|
|
122
|
-
},
|
|
123
|
-
);
|
|
124
|
-
});
|
|
125
|
-
}
|
|
204
|
+
setState((prev) => ({ ...prev, isProcessing: false }));
|
|
205
|
+
|
|
206
|
+
// Return result based on state
|
|
207
|
+
if (orchestrator.error) {
|
|
208
|
+
return { success: false, error: orchestrator.error.message };
|
|
126
209
|
}
|
|
127
210
|
|
|
128
|
-
return
|
|
129
|
-
effectivePrompt,
|
|
130
|
-
options,
|
|
131
|
-
{ userId, config, callbacks, buildInput, extractResult },
|
|
132
|
-
{ onStateUpdate: (update) => setState((prev) => ({ ...prev, ...update })) },
|
|
133
|
-
);
|
|
211
|
+
return orchestrator.result || { success: false, error: "No result" };
|
|
134
212
|
},
|
|
135
|
-
[state.prompt, callbacks,
|
|
213
|
+
[state.prompt, callbacks, orchestrator],
|
|
136
214
|
);
|
|
137
215
|
|
|
138
216
|
const reset = useCallback(() => {
|
|
139
217
|
setState(INITIAL_STATE);
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
const isReady = useMemo(
|
|
143
|
-
() => state.prompt.trim().length > 0 && !state.isProcessing,
|
|
144
|
-
[state.prompt, state.isProcessing],
|
|
145
|
-
);
|
|
218
|
+
orchestrator.reset();
|
|
219
|
+
}, [orchestrator]);
|
|
146
220
|
|
|
147
|
-
const
|
|
148
|
-
|
|
149
|
-
[isReady, state.error],
|
|
150
|
-
);
|
|
221
|
+
const isReady = useMemo(() => state.prompt.trim().length > 0 && !state.isProcessing, [state.prompt, state.isProcessing]);
|
|
222
|
+
const canGenerate = useMemo(() => isReady && !state.error, [isReady, state.error]);
|
|
151
223
|
|
|
152
224
|
return { state, setPrompt, generate, reset, isReady, canGenerate };
|
|
153
225
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Generation Orchestrator
|
|
3
3
|
* Feature-agnostic hook for AI generation with centralized:
|
|
4
|
+
* - Auth checking (optional)
|
|
5
|
+
* - Content moderation (optional)
|
|
4
6
|
* - Credit management
|
|
5
7
|
* - Error handling
|
|
6
8
|
* - Alert display
|
|
@@ -32,24 +34,100 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
32
34
|
strategy: GenerationStrategy<TInput, TResult>,
|
|
33
35
|
config: GenerationConfig,
|
|
34
36
|
): UseGenerationOrchestratorReturn<TInput, TResult> => {
|
|
35
|
-
const { userId, alertMessages, onCreditsExhausted, onSuccess, onError } = config;
|
|
37
|
+
const { userId, alertMessages, onCreditsExhausted, onSuccess, onError, auth, moderation, credits } = config;
|
|
36
38
|
|
|
37
39
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
38
|
-
console.log("[Orchestrator] Hook initialized:", {
|
|
40
|
+
console.log("[Orchestrator] Hook initialized:", {
|
|
41
|
+
userId,
|
|
42
|
+
hasAuth: !!auth,
|
|
43
|
+
hasModeration: !!moderation,
|
|
44
|
+
hasCreditsCallbacks: !!credits,
|
|
45
|
+
});
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
const [state, setState] = useState<GenerationState<TResult>>(INITIAL_STATE);
|
|
42
49
|
const isGeneratingRef = useRef(false);
|
|
50
|
+
const pendingInputRef = useRef<TInput | null>(null);
|
|
43
51
|
const offlineStore = useOfflineStore();
|
|
44
52
|
const { showError, showSuccess } = useAlert();
|
|
45
|
-
const
|
|
53
|
+
const defaultCredits = useDeductCredit({ userId, onCreditsExhausted });
|
|
54
|
+
|
|
55
|
+
// Use provided credit callbacks or default to useDeductCredit hook
|
|
56
|
+
const checkCredits = credits?.checkCredits ?? defaultCredits.checkCredits;
|
|
57
|
+
const deductCredit = credits?.deductCredits ?? defaultCredits.deductCredit;
|
|
58
|
+
const handleCreditsExhausted = credits?.onCreditsExhausted ?? onCreditsExhausted;
|
|
59
|
+
|
|
60
|
+
// Core execution logic (after all checks pass)
|
|
61
|
+
const executeGeneration = useCallback(
|
|
62
|
+
async (input: TInput) => {
|
|
63
|
+
const creditCost = strategy.getCreditCost();
|
|
64
|
+
|
|
65
|
+
setState((prev) => ({ ...prev, status: "generating", progress: 10 }));
|
|
66
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
67
|
+
console.log("[Orchestrator] 🎨 Starting strategy.execute()");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const result = await strategy.execute(input, (progress) => {
|
|
71
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
72
|
+
console.log("[Orchestrator] 📊 Progress update:", progress);
|
|
73
|
+
}
|
|
74
|
+
setState((prev) => ({ ...prev, progress }));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
78
|
+
console.log("[Orchestrator] ✅ strategy.execute() completed");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setState((prev) => ({ ...prev, progress: 70 }));
|
|
82
|
+
|
|
83
|
+
// Save result
|
|
84
|
+
if (strategy.save && userId) {
|
|
85
|
+
setState((prev) => ({ ...prev, status: "saving" }));
|
|
86
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
87
|
+
console.log("[Orchestrator] 💾 Saving result...");
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
await strategy.save(result, userId);
|
|
91
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
92
|
+
console.log("[Orchestrator] ✅ Save completed");
|
|
93
|
+
}
|
|
94
|
+
} catch (saveErr) {
|
|
95
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
96
|
+
console.log("[Orchestrator] ❌ Save failed:", saveErr);
|
|
97
|
+
}
|
|
98
|
+
throw createGenerationError("save", "Failed to save", saveErr instanceof Error ? saveErr : undefined);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setState((prev) => ({ ...prev, progress: 90 }));
|
|
103
|
+
|
|
104
|
+
// Deduct credit
|
|
105
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
106
|
+
console.log("[Orchestrator] 💳 Deducting credit:", creditCost);
|
|
107
|
+
}
|
|
108
|
+
await deductCredit(creditCost);
|
|
109
|
+
|
|
110
|
+
// Success
|
|
111
|
+
setState({ status: "success", isGenerating: false, progress: 100, result, error: null });
|
|
112
|
+
|
|
113
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
114
|
+
console.log("[Orchestrator] 🎉 Generation SUCCESS");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (alertMessages.success) {
|
|
118
|
+
void showSuccess("Success", alertMessages.success);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
onSuccess?.(result);
|
|
122
|
+
},
|
|
123
|
+
[strategy, userId, alertMessages, deductCredit, showSuccess, onSuccess],
|
|
124
|
+
);
|
|
46
125
|
|
|
47
126
|
const generate = useCallback(
|
|
48
127
|
async (input: TInput) => {
|
|
49
128
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
50
129
|
console.log("[Orchestrator] 🚀 generate() called");
|
|
51
130
|
console.log("[Orchestrator] Input:", JSON.stringify(input, null, 2).slice(0, 500));
|
|
52
|
-
console.log("[Orchestrator] isGeneratingRef:", isGeneratingRef.current);
|
|
53
131
|
}
|
|
54
132
|
|
|
55
133
|
if (isGeneratingRef.current) {
|
|
@@ -60,149 +138,141 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
60
138
|
}
|
|
61
139
|
|
|
62
140
|
isGeneratingRef.current = true;
|
|
141
|
+
pendingInputRef.current = input;
|
|
63
142
|
setState({ ...INITIAL_STATE, status: "checking", isGenerating: true });
|
|
64
143
|
|
|
65
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
66
|
-
console.log("[Orchestrator] State set to: checking, isGenerating: true");
|
|
67
|
-
}
|
|
68
|
-
|
|
69
144
|
try {
|
|
70
|
-
//
|
|
145
|
+
// 1. Auth check (optional)
|
|
146
|
+
if (auth) {
|
|
147
|
+
setState((prev) => ({ ...prev, status: "authenticating" }));
|
|
148
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
149
|
+
console.log("[Orchestrator] 🔐 Checking authentication...");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (!auth.isAuthenticated()) {
|
|
153
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
154
|
+
console.log("[Orchestrator] ❌ Not authenticated");
|
|
155
|
+
}
|
|
156
|
+
isGeneratingRef.current = false;
|
|
157
|
+
setState(INITIAL_STATE);
|
|
158
|
+
auth.onAuthRequired?.();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
162
|
+
console.log("[Orchestrator] ✅ Authentication passed");
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 2. Network check
|
|
71
167
|
if (!offlineStore.isOnline) {
|
|
72
168
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
73
169
|
console.log("[Orchestrator] ❌ Network check failed - offline");
|
|
74
170
|
}
|
|
75
171
|
throw createGenerationError("network", "No internet connection");
|
|
76
172
|
}
|
|
77
|
-
|
|
78
173
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
79
174
|
console.log("[Orchestrator] ✅ Network check passed");
|
|
80
175
|
}
|
|
81
176
|
|
|
82
|
-
// Credit check
|
|
177
|
+
// 3. Credit check
|
|
83
178
|
const creditCost = strategy.getCreditCost();
|
|
84
179
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
85
180
|
console.log("[Orchestrator] 💳 Credit cost:", creditCost);
|
|
86
181
|
}
|
|
87
182
|
|
|
88
183
|
const hasCredits = await checkCredits(creditCost);
|
|
89
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
90
|
-
console.log("[Orchestrator] 💳 Has credits:", hasCredits);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
184
|
if (!hasCredits) {
|
|
94
185
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
95
186
|
console.log("[Orchestrator] ❌ No credits, opening paywall");
|
|
96
187
|
}
|
|
97
188
|
isGeneratingRef.current = false;
|
|
98
189
|
setState(INITIAL_STATE);
|
|
99
|
-
|
|
190
|
+
handleCreditsExhausted?.();
|
|
100
191
|
return;
|
|
101
192
|
}
|
|
102
|
-
|
|
103
|
-
// Start generation
|
|
104
|
-
setState((prev) => ({ ...prev, status: "generating", progress: 10 }));
|
|
105
193
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
106
|
-
console.log("[Orchestrator]
|
|
194
|
+
console.log("[Orchestrator] ✅ Credit check passed");
|
|
107
195
|
}
|
|
108
196
|
|
|
109
|
-
|
|
197
|
+
// 4. Content moderation (optional)
|
|
198
|
+
if (moderation) {
|
|
199
|
+
setState((prev) => ({ ...prev, status: "moderating" }));
|
|
110
200
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
111
|
-
console.log("[Orchestrator]
|
|
201
|
+
console.log("[Orchestrator] 🛡️ Checking content moderation...");
|
|
112
202
|
}
|
|
113
|
-
setState((prev) => ({ ...prev, progress }));
|
|
114
|
-
});
|
|
115
203
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
console.log("[Orchestrator] Result type:", typeof result);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
setState((prev) => ({ ...prev, progress: 70 }));
|
|
122
|
-
|
|
123
|
-
// Save result
|
|
124
|
-
if (strategy.save && userId) {
|
|
125
|
-
setState((prev) => ({ ...prev, status: "saving" }));
|
|
126
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
127
|
-
console.log("[Orchestrator] 💾 Saving result...");
|
|
128
|
-
}
|
|
129
|
-
try {
|
|
130
|
-
await strategy.save(result, userId);
|
|
204
|
+
const moderationResult = await moderation.checkContent(input);
|
|
205
|
+
if (!moderationResult.allowed && moderationResult.warnings.length > 0) {
|
|
131
206
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
132
|
-
console.log("[Orchestrator]
|
|
207
|
+
console.log("[Orchestrator] ⚠️ Moderation warnings:", moderationResult.warnings);
|
|
133
208
|
}
|
|
134
|
-
|
|
135
|
-
if (
|
|
136
|
-
|
|
209
|
+
|
|
210
|
+
if (moderation.onShowWarning) {
|
|
211
|
+
// Show warning and let user decide
|
|
212
|
+
moderation.onShowWarning(
|
|
213
|
+
moderationResult.warnings,
|
|
214
|
+
() => {
|
|
215
|
+
// User cancelled
|
|
216
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
217
|
+
console.log("[Orchestrator] User cancelled after moderation warning");
|
|
218
|
+
}
|
|
219
|
+
isGeneratingRef.current = false;
|
|
220
|
+
setState(INITIAL_STATE);
|
|
221
|
+
},
|
|
222
|
+
async () => {
|
|
223
|
+
// User continued - execute generation
|
|
224
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
225
|
+
console.log("[Orchestrator] User continued after moderation warning");
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
await executeGeneration(input);
|
|
229
|
+
} catch (err) {
|
|
230
|
+
const error = parseError(err);
|
|
231
|
+
setState({ status: "error", isGenerating: false, progress: 0, result: null, error });
|
|
232
|
+
void showError("Error", getAlertMessage(error, alertMessages));
|
|
233
|
+
onError?.(error);
|
|
234
|
+
} finally {
|
|
235
|
+
isGeneratingRef.current = false;
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
);
|
|
239
|
+
return; // Exit here - callback will handle the rest
|
|
137
240
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
);
|
|
241
|
+
// No warning handler - block the request
|
|
242
|
+
throw createGenerationError("policy", "Content policy violation");
|
|
243
|
+
}
|
|
244
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
245
|
+
console.log("[Orchestrator] ✅ Moderation passed");
|
|
143
246
|
}
|
|
144
247
|
}
|
|
145
248
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
// Deduct credit
|
|
149
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
150
|
-
console.log("[Orchestrator] 💳 Deducting credit:", creditCost);
|
|
151
|
-
}
|
|
152
|
-
await deductCredit(creditCost);
|
|
153
|
-
|
|
154
|
-
// Success
|
|
155
|
-
setState({
|
|
156
|
-
status: "success",
|
|
157
|
-
isGenerating: false,
|
|
158
|
-
progress: 100,
|
|
159
|
-
result,
|
|
160
|
-
error: null,
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
164
|
-
console.log("[Orchestrator] 🎉 Generation SUCCESS");
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (alertMessages.success) {
|
|
168
|
-
void showSuccess("Success", alertMessages.success);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
onSuccess?.(result);
|
|
249
|
+
// 5. Execute generation
|
|
250
|
+
await executeGeneration(input);
|
|
172
251
|
} catch (err) {
|
|
173
252
|
const error = parseError(err);
|
|
174
|
-
|
|
175
253
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
176
254
|
console.log("[Orchestrator] ❌ Generation ERROR:", error);
|
|
177
255
|
}
|
|
178
|
-
|
|
179
|
-
setState({
|
|
180
|
-
status: "error",
|
|
181
|
-
isGenerating: false,
|
|
182
|
-
progress: 0,
|
|
183
|
-
result: null,
|
|
184
|
-
error,
|
|
185
|
-
});
|
|
186
|
-
|
|
256
|
+
setState({ status: "error", isGenerating: false, progress: 0, result: null, error });
|
|
187
257
|
void showError("Error", getAlertMessage(error, alertMessages));
|
|
188
258
|
onError?.(error);
|
|
189
259
|
} finally {
|
|
190
260
|
isGeneratingRef.current = false;
|
|
191
261
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
192
|
-
console.log("[Orchestrator] 🏁 generate() finished
|
|
262
|
+
console.log("[Orchestrator] 🏁 generate() finished");
|
|
193
263
|
}
|
|
194
264
|
}
|
|
195
265
|
},
|
|
196
266
|
[
|
|
267
|
+
auth,
|
|
268
|
+
moderation,
|
|
197
269
|
strategy,
|
|
198
|
-
userId,
|
|
199
270
|
alertMessages,
|
|
200
271
|
offlineStore.isOnline,
|
|
201
272
|
checkCredits,
|
|
202
|
-
|
|
273
|
+
handleCreditsExhausted,
|
|
274
|
+
executeGeneration,
|
|
203
275
|
showError,
|
|
204
|
-
showSuccess,
|
|
205
|
-
onSuccess,
|
|
206
276
|
onError,
|
|
207
277
|
],
|
|
208
278
|
);
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
export type OrchestratorStatus =
|
|
7
7
|
| "idle"
|
|
8
8
|
| "checking"
|
|
9
|
+
| "authenticating"
|
|
10
|
+
| "moderating"
|
|
9
11
|
| "generating"
|
|
10
12
|
| "saving"
|
|
11
13
|
| "success"
|
|
@@ -30,6 +32,43 @@ export interface AlertMessages {
|
|
|
30
32
|
creditFailed: string;
|
|
31
33
|
unknown: string;
|
|
32
34
|
success?: string;
|
|
35
|
+
authRequired?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Content moderation result */
|
|
39
|
+
export interface ModerationResult {
|
|
40
|
+
allowed: boolean;
|
|
41
|
+
warnings: string[];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Auth callbacks for features that need authentication */
|
|
45
|
+
export interface AuthCallbacks {
|
|
46
|
+
/** Check if user is authenticated */
|
|
47
|
+
isAuthenticated: () => boolean;
|
|
48
|
+
/** Called when auth is required */
|
|
49
|
+
onAuthRequired?: () => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Moderation callbacks for content filtering */
|
|
53
|
+
export interface ModerationCallbacks {
|
|
54
|
+
/** Check content for policy violations */
|
|
55
|
+
checkContent: (input: unknown) => Promise<ModerationResult>;
|
|
56
|
+
/** Show moderation warning with continue/cancel options */
|
|
57
|
+
onShowWarning?: (
|
|
58
|
+
warnings: string[],
|
|
59
|
+
onCancel: () => void,
|
|
60
|
+
onContinue: () => void,
|
|
61
|
+
) => void;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Credit callbacks for features that manage credits via callbacks */
|
|
65
|
+
export interface CreditCallbacks {
|
|
66
|
+
/** Check if user can afford the cost */
|
|
67
|
+
checkCredits: (cost: number) => Promise<boolean>;
|
|
68
|
+
/** Deduct credits after successful generation */
|
|
69
|
+
deductCredits: (cost: number) => Promise<void>;
|
|
70
|
+
/** Called when credits are exhausted */
|
|
71
|
+
onCreditsExhausted?: () => void;
|
|
33
72
|
}
|
|
34
73
|
|
|
35
74
|
export interface GenerationConfig {
|
|
@@ -38,6 +77,12 @@ export interface GenerationConfig {
|
|
|
38
77
|
onCreditsExhausted?: () => void;
|
|
39
78
|
onSuccess?: (result: unknown) => void;
|
|
40
79
|
onError?: (error: GenerationError) => void;
|
|
80
|
+
/** Optional auth callbacks - if not provided, auth is skipped */
|
|
81
|
+
auth?: AuthCallbacks;
|
|
82
|
+
/** Optional moderation callbacks - if not provided, moderation is skipped */
|
|
83
|
+
moderation?: ModerationCallbacks;
|
|
84
|
+
/** Optional credit callbacks - if provided, overrides default useDeductCredit */
|
|
85
|
+
credits?: CreditCallbacks;
|
|
41
86
|
}
|
|
42
87
|
|
|
43
88
|
export interface GenerationState<TResult> {
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useImageToVideoFeature Constants
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { ImageToVideoFeatureState } from "../../domain/types";
|
|
6
|
-
|
|
7
|
-
export const INITIAL_IMAGE_TO_VIDEO_STATE: ImageToVideoFeatureState = {
|
|
8
|
-
imageUri: null,
|
|
9
|
-
motionPrompt: "",
|
|
10
|
-
videoUrl: null,
|
|
11
|
-
thumbnailUrl: null,
|
|
12
|
-
isProcessing: false,
|
|
13
|
-
progress: 0,
|
|
14
|
-
error: null,
|
|
15
|
-
};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useImageToVideoFeature Type Definitions
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type {
|
|
6
|
-
ImageToVideoFeatureState,
|
|
7
|
-
ImageToVideoFeatureConfig,
|
|
8
|
-
ImageToVideoResult,
|
|
9
|
-
ImageToVideoFeatureCallbacks,
|
|
10
|
-
ImageToVideoGenerateParams,
|
|
11
|
-
} from "../../domain/types";
|
|
12
|
-
|
|
13
|
-
export interface UseImageToVideoFeatureProps {
|
|
14
|
-
config: ImageToVideoFeatureConfig;
|
|
15
|
-
callbacks?: ImageToVideoFeatureCallbacks;
|
|
16
|
-
userId: string;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface UseImageToVideoFeatureReturn {
|
|
20
|
-
state: ImageToVideoFeatureState;
|
|
21
|
-
setImageUri: (uri: string) => void;
|
|
22
|
-
setMotionPrompt: (prompt: string) => void;
|
|
23
|
-
generate: (params?: ImageToVideoGenerateParams) => Promise<ImageToVideoResult>;
|
|
24
|
-
reset: () => void;
|
|
25
|
-
isReady: boolean;
|
|
26
|
-
canGenerate: boolean;
|
|
27
|
-
}
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Text-to-Video Execution
|
|
3
|
-
* Core execution logic for text-to-video generation
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { executeTextToVideo } from "../../infrastructure/services";
|
|
7
|
-
import type {
|
|
8
|
-
TextToVideoConfig,
|
|
9
|
-
TextToVideoCallbacks,
|
|
10
|
-
TextToVideoResult,
|
|
11
|
-
TextToVideoOptions,
|
|
12
|
-
TextToVideoInputBuilder,
|
|
13
|
-
TextToVideoResultExtractor,
|
|
14
|
-
} from "../../domain/types";
|
|
15
|
-
|
|
16
|
-
declare const __DEV__: boolean;
|
|
17
|
-
|
|
18
|
-
export interface ExecutionContext {
|
|
19
|
-
userId: string;
|
|
20
|
-
config: TextToVideoConfig;
|
|
21
|
-
callbacks: TextToVideoCallbacks;
|
|
22
|
-
buildInput: TextToVideoInputBuilder;
|
|
23
|
-
extractResult?: TextToVideoResultExtractor;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface ExecutionCallbacks {
|
|
27
|
-
onStateUpdate: (update: {
|
|
28
|
-
isProcessing?: boolean;
|
|
29
|
-
progress?: number;
|
|
30
|
-
videoUrl?: string | null;
|
|
31
|
-
thumbnailUrl?: string | null;
|
|
32
|
-
error?: string | null;
|
|
33
|
-
}) => void;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function generateCreationId(): string {
|
|
37
|
-
return `text-to-video_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export async function executeVideoGeneration(
|
|
41
|
-
prompt: string,
|
|
42
|
-
options: TextToVideoOptions | undefined,
|
|
43
|
-
context: ExecutionContext,
|
|
44
|
-
executionCallbacks: ExecutionCallbacks,
|
|
45
|
-
): Promise<TextToVideoResult> {
|
|
46
|
-
const { userId, config, callbacks, buildInput, extractResult } = context;
|
|
47
|
-
const { onStateUpdate } = executionCallbacks;
|
|
48
|
-
const creationId = generateCreationId();
|
|
49
|
-
|
|
50
|
-
onStateUpdate({ isProcessing: true, progress: 0, error: null });
|
|
51
|
-
|
|
52
|
-
if (__DEV__) {
|
|
53
|
-
console.log(
|
|
54
|
-
"[TextToVideoFeature] Starting generation with prompt:",
|
|
55
|
-
prompt,
|
|
56
|
-
"creationId:",
|
|
57
|
-
creationId,
|
|
58
|
-
);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (callbacks.onGenerationStart) {
|
|
62
|
-
callbacks
|
|
63
|
-
.onGenerationStart({
|
|
64
|
-
creationId,
|
|
65
|
-
type: "text-to-video",
|
|
66
|
-
prompt,
|
|
67
|
-
metadata: options as Record<string, unknown> | undefined,
|
|
68
|
-
})
|
|
69
|
-
.catch((err) => {
|
|
70
|
-
if (__DEV__) {
|
|
71
|
-
console.warn("[TextToVideoFeature] onGenerationStart failed:", err);
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (__DEV__) {
|
|
77
|
-
console.log("[TextToVideoFeature] Starting executeTextToVideo...");
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
try {
|
|
81
|
-
const result = await executeTextToVideo(
|
|
82
|
-
{ prompt, userId, options },
|
|
83
|
-
{
|
|
84
|
-
model: config.model,
|
|
85
|
-
buildInput,
|
|
86
|
-
extractResult,
|
|
87
|
-
onProgress: (progress) => {
|
|
88
|
-
onStateUpdate({ progress });
|
|
89
|
-
callbacks.onProgress?.(progress);
|
|
90
|
-
},
|
|
91
|
-
},
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
if (result.success && result.videoUrl) {
|
|
95
|
-
onStateUpdate({
|
|
96
|
-
videoUrl: result.videoUrl,
|
|
97
|
-
thumbnailUrl: result.thumbnailUrl ?? null,
|
|
98
|
-
isProcessing: false,
|
|
99
|
-
progress: 100,
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
if (callbacks.onCreditDeduct) {
|
|
103
|
-
await callbacks.onCreditDeduct(config.creditCost);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (callbacks.onCreationSave) {
|
|
107
|
-
await callbacks.onCreationSave({
|
|
108
|
-
creationId,
|
|
109
|
-
type: "text-to-video",
|
|
110
|
-
videoUrl: result.videoUrl,
|
|
111
|
-
thumbnailUrl: result.thumbnailUrl,
|
|
112
|
-
prompt,
|
|
113
|
-
metadata: options as Record<string, unknown> | undefined,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
callbacks.onGenerate?.(result);
|
|
118
|
-
} else {
|
|
119
|
-
const error = result.error || "Generation failed";
|
|
120
|
-
onStateUpdate({ isProcessing: false, error });
|
|
121
|
-
callbacks.onError?.(error);
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
return result;
|
|
125
|
-
} catch (err) {
|
|
126
|
-
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
127
|
-
if (__DEV__) {
|
|
128
|
-
console.error("[TextToVideoFeature] Generation error:", errorMessage);
|
|
129
|
-
}
|
|
130
|
-
onStateUpdate({ isProcessing: false, error: errorMessage });
|
|
131
|
-
callbacks.onError?.(errorMessage);
|
|
132
|
-
return { success: false, error: errorMessage };
|
|
133
|
-
}
|
|
134
|
-
}
|