@umituz/react-native-ai-generation-content 1.90.5 → 1.90.7
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/domains/access-control/hooks/useAIFeatureGate.ts +2 -2
- package/src/domains/creations/domain/constants/index.ts +21 -0
- package/src/domains/creations/domain/utils/creation-format.util.ts +8 -1
- package/src/domains/creations/domain/utils/index.ts +5 -1
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-couple-preparation.ts +1 -1
- package/src/domains/generation/infrastructure/couple-generation-builder/builder-scenario.ts +1 -1
- package/src/domains/generation/presentation/useAIGeneration.hook.ts +2 -2
- package/src/domains/generation/wizard/presentation/hooks/photo-upload/types.ts +4 -1
- package/src/domains/generation/wizard/presentation/hooks/photo-upload/usePhotoUploadStateLogic.ts +1 -1
- package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationCallbacks.ts +1 -1
- package/src/domains/generation/wizard/presentation/hooks/video-queue/useVideoQueueGenerationPolling.ts +1 -1
- package/src/domains/image-to-video/domain/index.ts +6 -0
- package/src/domains/image-to-video/presentation/components/index.ts +2 -2
- package/src/domains/image-to-video/presentation/hooks/image-to-video-feature.types.ts +1 -0
- package/src/domains/image-to-video/presentation/index.ts +55 -0
- package/src/domains/text-to-image/infrastructure/services/text-to-image-executor.ts +7 -1
- package/src/domains/text-to-video/domain/index.ts +6 -0
- package/src/domains/text-to-video/domain/types/state.types.ts +17 -0
- package/src/domains/text-to-video/infrastructure/services/text-to-video-executor.ts +7 -1
- package/src/domains/text-to-video/presentation/hooks/index.ts +1 -1
- package/src/domains/text-to-video/presentation/hooks/textToVideoStrategy.ts +17 -1
- package/src/domains/text-to-video/presentation/hooks/useTextToVideoFeature.ts +11 -4
- package/src/domains/text-to-video/presentation/index.ts +33 -0
- package/src/infrastructure/config/app-services.config.ts +1 -1
- package/src/infrastructure/executors/base-executor.ts +2 -2
- package/src/presentation/hooks/generation/orchestrator.ts +131 -0
- package/src/presentation/hooks/generation/useDualImageGeneration.ts +2 -2
- package/src/presentation/hooks/generation/useImageGeneration.ts +2 -2
- package/src/presentation/hooks/generation/useVideoGeneration.ts +2 -2
- package/src/shared/hooks/factories/createGenerationHook.ts +142 -0
- package/src/shared-kernel/infrastructure/validation/index.ts +8 -2
- /package/src/domains/generation/infrastructure/{appearance-analysis → appearance-analysis-module}/index.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.90.
|
|
3
|
+
"version": "1.90.7",
|
|
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",
|
|
@@ -15,7 +15,6 @@ import { useCallback, useMemo } from "react";
|
|
|
15
15
|
import { useOffline } from "@umituz/react-native-design-system/offline";
|
|
16
16
|
import { useAuth, useAuthModalStore } from "@umituz/react-native-auth";
|
|
17
17
|
import {
|
|
18
|
-
usePremiumStatus,
|
|
19
18
|
useCredits,
|
|
20
19
|
usePaywallVisibility,
|
|
21
20
|
useFeatureGate,
|
|
@@ -47,11 +46,12 @@ export function useAIFeatureGate(options: AIFeatureGateOptions): AIFeatureGateRe
|
|
|
47
46
|
const { isOffline } = useOffline();
|
|
48
47
|
const { hasFirebaseUser } = useAuth();
|
|
49
48
|
const { showAuthModal } = useAuthModalStore();
|
|
50
|
-
const { isPremium } = usePremiumStatus();
|
|
51
49
|
const { credits, isCreditsLoaded, isLoading: isCreditsLoading } = useCredits();
|
|
52
50
|
const { openPaywall } = usePaywallVisibility();
|
|
53
51
|
const creditBalance = credits?.credits ?? 0;
|
|
54
52
|
const hasCredits = creditBalance >= creditCost;
|
|
53
|
+
// TODO: Replace with actual premium status check from subscription package
|
|
54
|
+
const isPremium = false;
|
|
55
55
|
|
|
56
56
|
const { requireFeature: requireFeatureFromPackage } = useFeatureGate({
|
|
57
57
|
isAuthenticated: hasFirebaseUser,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creation Domain Constants
|
|
3
|
+
* Centralized constants for creation field names, status, and validation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
CREATION_FIELDS,
|
|
8
|
+
UPDATABLE_FIELDS,
|
|
9
|
+
} from './creation-fields.constants';
|
|
10
|
+
|
|
11
|
+
// Re-export CreationFieldName as a type alias
|
|
12
|
+
import type { CreationFieldName } from './creation-fields.constants';
|
|
13
|
+
export type { CreationFieldName };
|
|
14
|
+
|
|
15
|
+
export {
|
|
16
|
+
CREATION_STATUS,
|
|
17
|
+
} from './creation-status.constants';
|
|
18
|
+
|
|
19
|
+
export {
|
|
20
|
+
CREATION_VALIDATION,
|
|
21
|
+
} from './creation-validation.constants';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Creation Format Utilities
|
|
3
|
-
* Single Responsibility: Text formatting
|
|
3
|
+
* Single Responsibility: Text formatting and ID generation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -10,3 +10,10 @@ export function truncateText(text: string, maxLength: number): string {
|
|
|
10
10
|
if (text.length <= maxLength) return text;
|
|
11
11
|
return text.substring(0, maxLength - 3) + "...";
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Generate unique creation ID
|
|
16
|
+
*/
|
|
17
|
+
export function generateCreationId(): string {
|
|
18
|
+
return `creation_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
19
|
+
}
|
|
@@ -5,11 +5,15 @@
|
|
|
5
5
|
|
|
6
6
|
// Display utilities
|
|
7
7
|
export {
|
|
8
|
-
|
|
8
|
+
getTypeIcon,
|
|
9
|
+
getTypeTextKey,
|
|
10
|
+
getTypeText,
|
|
11
|
+
getCreationTitle,
|
|
9
12
|
} from './creation-display.util';
|
|
10
13
|
|
|
11
14
|
// Format utilities
|
|
12
15
|
export {
|
|
16
|
+
truncateText,
|
|
13
17
|
generateCreationId,
|
|
14
18
|
} from './creation-format.util';
|
|
15
19
|
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Handles photo URI extraction and appearance analysis
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { getAppearanceContext } from "
|
|
7
|
+
import { getAppearanceContext } from "../appearance-analysis";
|
|
8
8
|
import { logBuilderStep } from "./utils";
|
|
9
9
|
import type { CoupleGenerationInputParams } from "./types";
|
|
10
10
|
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
refinePromptForCouple,
|
|
10
10
|
prependContext,
|
|
11
11
|
} from "../../../../infrastructure/utils/couple-input.util";
|
|
12
|
-
import { getAppearanceContext } from "
|
|
12
|
+
import { getAppearanceContext } from "../appearance-analysis";
|
|
13
13
|
import type {
|
|
14
14
|
ScenarioGenerationInputParams,
|
|
15
15
|
CoupleGenerationInput,
|
|
@@ -82,8 +82,8 @@ export function useAIGeneration(
|
|
|
82
82
|
const orchestrator = useGenerationOrchestrator(strategy, {
|
|
83
83
|
userId,
|
|
84
84
|
alertMessages: alertMessages || DEFAULT_ALERT_MESSAGES,
|
|
85
|
-
onSuccess,
|
|
86
|
-
onError: onError ? (error) => onError(error.message) : undefined,
|
|
85
|
+
onSuccess: async (result) => onSuccess?.(result),
|
|
86
|
+
onError: onError ? async (error) => onError(error.message) : undefined,
|
|
87
87
|
});
|
|
88
88
|
|
|
89
89
|
return {
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
* Generic Photo Upload State Hook - Type Definitions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import type { UploadedImage } from "
|
|
5
|
+
import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
|
|
6
|
+
|
|
7
|
+
// Re-export for use in this module
|
|
8
|
+
export type { UploadedImage };
|
|
6
9
|
|
|
7
10
|
export interface PhotoUploadConfig {
|
|
8
11
|
readonly maxFileSizeMB?: number;
|
package/src/domains/generation/wizard/presentation/hooks/photo-upload/usePhotoUploadStateLogic.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { useCallback, useRef, useEffect } from "react";
|
|
6
6
|
import { useMedia, MediaQuality, MediaValidationError, MEDIA_CONSTANTS } from "@umituz/react-native-design-system/media";
|
|
7
|
-
import type { UploadedImage } from "
|
|
7
|
+
import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
|
|
8
8
|
import type { PhotoUploadConfig, PhotoUploadError, PhotoUploadTranslations } from "./types";
|
|
9
9
|
|
|
10
10
|
export function usePhotoUploadStateLogic(
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { useCallback } from "react";
|
|
6
|
-
import type { GenerationUrls } from "
|
|
6
|
+
import type { GenerationUrls } from "../generation-result.utils";
|
|
7
7
|
import type {
|
|
8
8
|
UseVideoQueueGenerationProps,
|
|
9
9
|
} from "../use-video-queue-generation.types";
|
|
@@ -56,7 +56,7 @@ export function usePollStatus(
|
|
|
56
56
|
* Hook to sync callback refs
|
|
57
57
|
*/
|
|
58
58
|
export function useCallbackRefs(
|
|
59
|
-
handleComplete: (urls: import("
|
|
59
|
+
handleComplete: (urls: import("../generation-result.utils").GenerationUrls) => Promise<void>,
|
|
60
60
|
handleError: (errorMsg: string) => Promise<void>,
|
|
61
61
|
pollStatus: () => Promise<void>,
|
|
62
62
|
refs: VideoQueueRefs,
|
|
@@ -18,5 +18,5 @@ export type {
|
|
|
18
18
|
} from "./ImageSelectionGrid";
|
|
19
19
|
|
|
20
20
|
// Action Components
|
|
21
|
-
export { GenerateButton as ImageToVideoGenerateButton } from "
|
|
22
|
-
export type { GenerateButtonProps as ImageToVideoGenerateButtonProps } from "
|
|
21
|
+
export { GenerateButton as ImageToVideoGenerateButton } from "../../../../../presentation/components/buttons/GenerateButton";
|
|
22
|
+
export type { GenerateButtonProps as ImageToVideoGenerateButtonProps } from "../../../../../presentation/components/buttons/GenerateButton";
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image-to-Video Presentation Index
|
|
3
|
+
* Exports all presentation layer items (hooks and components)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// HOOKS
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
// Form State Hook
|
|
11
|
+
export { useImageToVideoFormState } from "./hooks";
|
|
12
|
+
export type {
|
|
13
|
+
UseImageToVideoFormStateOptions,
|
|
14
|
+
UseImageToVideoFormStateReturn,
|
|
15
|
+
} from "./hooks";
|
|
16
|
+
|
|
17
|
+
// Generation Hook
|
|
18
|
+
export { useImageToVideoGeneration } from "./hooks";
|
|
19
|
+
export type {
|
|
20
|
+
UseImageToVideoGenerationOptions,
|
|
21
|
+
UseImageToVideoGenerationReturn,
|
|
22
|
+
} from "./hooks";
|
|
23
|
+
|
|
24
|
+
// Combined Form Hook
|
|
25
|
+
export { useImageToVideoForm } from "./hooks";
|
|
26
|
+
export type {
|
|
27
|
+
UseImageToVideoFormOptions,
|
|
28
|
+
UseImageToVideoFormReturn,
|
|
29
|
+
} from "./hooks";
|
|
30
|
+
|
|
31
|
+
// Provider-based Feature Hook
|
|
32
|
+
export { useImageToVideoFeature } from "./hooks";
|
|
33
|
+
export type {
|
|
34
|
+
UseImageToVideoFeatureProps,
|
|
35
|
+
UseImageToVideoFeatureReturn,
|
|
36
|
+
} from "./hooks";
|
|
37
|
+
|
|
38
|
+
// =============================================================================
|
|
39
|
+
// COMPONENTS
|
|
40
|
+
// =============================================================================
|
|
41
|
+
|
|
42
|
+
export {
|
|
43
|
+
ImageToVideoAnimationStyleSelector,
|
|
44
|
+
ImageToVideoDurationSelector,
|
|
45
|
+
ImageToVideoSelectionGrid,
|
|
46
|
+
ImageToVideoGenerateButton,
|
|
47
|
+
} from "./components";
|
|
48
|
+
|
|
49
|
+
export type {
|
|
50
|
+
ImageToVideoAnimationStyleSelectorProps,
|
|
51
|
+
ImageToVideoDurationSelectorProps,
|
|
52
|
+
ImageToVideoSelectionGridProps,
|
|
53
|
+
ImageToVideoSelectionGridTranslations,
|
|
54
|
+
ImageToVideoGenerateButtonProps,
|
|
55
|
+
} from "./components";
|
|
@@ -60,9 +60,11 @@ class TextToImageExecutor extends BaseExecutor<
|
|
|
60
60
|
|
|
61
61
|
protected transformResult(
|
|
62
62
|
extracted: ExtractedImageResult,
|
|
63
|
+
request: TextToImageRequest,
|
|
63
64
|
): TextToImageResult {
|
|
64
65
|
return {
|
|
65
66
|
success: true,
|
|
67
|
+
requestId: request.meta.requestId,
|
|
66
68
|
imageUrl: extracted.imageUrl,
|
|
67
69
|
imageUrls: extracted.imageUrls,
|
|
68
70
|
};
|
|
@@ -100,7 +102,11 @@ export async function executeTextToImage(
|
|
|
100
102
|
if (isSuccess(result)) {
|
|
101
103
|
return result.value;
|
|
102
104
|
}
|
|
103
|
-
return {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
error: result.error,
|
|
108
|
+
requestId: request.meta.requestId,
|
|
109
|
+
};
|
|
104
110
|
}
|
|
105
111
|
|
|
106
112
|
/**
|
|
@@ -38,6 +38,23 @@ export interface FrameData {
|
|
|
38
38
|
order: number;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Text-to-video generation state
|
|
43
|
+
* Tracks the progress of video generation
|
|
44
|
+
*/
|
|
45
|
+
export interface TextToVideoGenerationState {
|
|
46
|
+
isGenerating: boolean;
|
|
47
|
+
error: string | null;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Initial generation state
|
|
52
|
+
*/
|
|
53
|
+
export const INITIAL_GENERATION_STATE: TextToVideoGenerationState = {
|
|
54
|
+
isGenerating: false,
|
|
55
|
+
error: null,
|
|
56
|
+
};
|
|
57
|
+
|
|
41
58
|
/**
|
|
42
59
|
* Initial form state
|
|
43
60
|
*/
|
|
@@ -82,9 +82,11 @@ class TextToVideoExecutor extends BaseExecutor<
|
|
|
82
82
|
|
|
83
83
|
protected transformResult(
|
|
84
84
|
extracted: ExtractedVideoResult,
|
|
85
|
+
request: TextToVideoRequest,
|
|
85
86
|
): TextToVideoResult {
|
|
86
87
|
return {
|
|
87
88
|
success: true,
|
|
89
|
+
requestId: request.meta.requestId,
|
|
88
90
|
videoUrl: extracted.videoUrl,
|
|
89
91
|
thumbnailUrl: extracted.thumbnailUrl,
|
|
90
92
|
};
|
|
@@ -122,7 +124,11 @@ export async function executeTextToVideo(
|
|
|
122
124
|
if (isSuccess(result)) {
|
|
123
125
|
return result.value;
|
|
124
126
|
}
|
|
125
|
-
return {
|
|
127
|
+
return {
|
|
128
|
+
success: false,
|
|
129
|
+
error: result.error,
|
|
130
|
+
requestId: request.meta.requestId,
|
|
131
|
+
};
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
/**
|
|
@@ -5,10 +5,12 @@ import type {
|
|
|
5
5
|
TextToVideoConfig,
|
|
6
6
|
TextToVideoCallbacks,
|
|
7
7
|
TextToVideoResult,
|
|
8
|
+
TextToVideoRequest,
|
|
8
9
|
TextToVideoOptions,
|
|
9
10
|
TextToVideoInputBuilder,
|
|
10
11
|
TextToVideoResultExtractor,
|
|
11
12
|
} from "../../domain/types";
|
|
13
|
+
import type { BaseRequestMeta } from "../../../../shared-kernel/base-types";
|
|
12
14
|
|
|
13
15
|
interface VideoGenerationInput {
|
|
14
16
|
prompt: string;
|
|
@@ -36,6 +38,19 @@ export const createTextToVideoStrategy = (
|
|
|
36
38
|
execute: async (input) => {
|
|
37
39
|
creationIdRef.current = input.creationId;
|
|
38
40
|
|
|
41
|
+
const meta: BaseRequestMeta = {
|
|
42
|
+
requestId: input.creationId,
|
|
43
|
+
userId,
|
|
44
|
+
timestamp: Date.now(),
|
|
45
|
+
priority: "normal",
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const request: TextToVideoRequest = {
|
|
49
|
+
prompt: input.prompt,
|
|
50
|
+
options: input.options,
|
|
51
|
+
meta,
|
|
52
|
+
};
|
|
53
|
+
|
|
39
54
|
callbacks.onGenerationStart?.({
|
|
40
55
|
creationId: input.creationId,
|
|
41
56
|
type: "text-to-video",
|
|
@@ -48,7 +63,7 @@ export const createTextToVideoStrategy = (
|
|
|
48
63
|
});
|
|
49
64
|
|
|
50
65
|
const result = await executeTextToVideo(
|
|
51
|
-
|
|
66
|
+
request,
|
|
52
67
|
{ model: config.model, buildInput, extractResult },
|
|
53
68
|
);
|
|
54
69
|
|
|
@@ -60,6 +75,7 @@ export const createTextToVideoStrategy = (
|
|
|
60
75
|
|
|
61
76
|
return {
|
|
62
77
|
success: true,
|
|
78
|
+
requestId: result.requestId,
|
|
63
79
|
videoUrl: result.videoUrl,
|
|
64
80
|
thumbnailUrl: result.thumbnailUrl,
|
|
65
81
|
};
|
|
@@ -38,6 +38,7 @@ const INITIAL_STATE: TextToVideoFeatureState = {
|
|
|
38
38
|
prompt: "",
|
|
39
39
|
videoUrl: null,
|
|
40
40
|
thumbnailUrl: null,
|
|
41
|
+
output: null,
|
|
41
42
|
isProcessing: false,
|
|
42
43
|
progress: 0,
|
|
43
44
|
error: null,
|
|
@@ -84,23 +85,29 @@ export function useTextToVideoFeature(props: UseTextToVideoFeatureProps): UseTex
|
|
|
84
85
|
if (!prompt.trim()) {
|
|
85
86
|
const error = "Prompt is required";
|
|
86
87
|
setState((prev) => ({ ...prev, error }));
|
|
87
|
-
return { success: false, error };
|
|
88
|
+
return { success: false, error, requestId: generateCreationId("text-to-video") };
|
|
88
89
|
}
|
|
89
90
|
|
|
90
91
|
setState((prev) => ({ ...prev, isProcessing: true, error: null, progress: 0 }));
|
|
92
|
+
const requestId = generateCreationId("text-to-video");
|
|
91
93
|
|
|
92
94
|
try {
|
|
93
95
|
const result = await orchestrator.generate({
|
|
94
96
|
prompt: prompt.trim(),
|
|
95
97
|
options: params,
|
|
96
|
-
creationId:
|
|
98
|
+
creationId: requestId,
|
|
97
99
|
});
|
|
98
100
|
setState((prev) => ({ ...prev, isProcessing: false }));
|
|
99
|
-
return {
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
requestId,
|
|
104
|
+
videoUrl: (result as TextToVideoResult)?.videoUrl || null,
|
|
105
|
+
thumbnailUrl: (result as TextToVideoResult)?.thumbnailUrl || null,
|
|
106
|
+
};
|
|
100
107
|
} catch (error) {
|
|
101
108
|
const message = error instanceof Error ? error.message : "Generation failed";
|
|
102
109
|
setState((prev) => ({ ...prev, isProcessing: false, error: message }));
|
|
103
|
-
return { success: false, error: message };
|
|
110
|
+
return { success: false, error: message, requestId };
|
|
104
111
|
}
|
|
105
112
|
},
|
|
106
113
|
[state.prompt, orchestrator],
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text-to-Video Presentation Index
|
|
3
|
+
* Exports all presentation layer items (hooks and components)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// HOOKS
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
export { useTextToVideoFeature } from "./hooks";
|
|
11
|
+
export type {
|
|
12
|
+
UseTextToVideoFeatureProps,
|
|
13
|
+
UseTextToVideoFeatureReturn,
|
|
14
|
+
TextToVideoGenerateParams,
|
|
15
|
+
} from "./hooks";
|
|
16
|
+
|
|
17
|
+
export { useTextToVideoForm } from "./hooks";
|
|
18
|
+
export type {
|
|
19
|
+
UseTextToVideoFormProps,
|
|
20
|
+
UseTextToVideoFormReturn,
|
|
21
|
+
} from "./hooks";
|
|
22
|
+
|
|
23
|
+
// =============================================================================
|
|
24
|
+
// COMPONENTS
|
|
25
|
+
// =============================================================================
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
GenerationTabs,
|
|
29
|
+
FrameSelector,
|
|
30
|
+
OptionsPanel,
|
|
31
|
+
HeroSection,
|
|
32
|
+
HintCarousel,
|
|
33
|
+
} from "./components";
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Singleton for storing app-provided service implementations
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { IAppServices, PartialAppServices } from "../../domain/interfaces/app-services.interface";
|
|
6
|
+
import type { IAppServices, PartialAppServices } from "../../domain/interfaces/app-services-composite.interface";
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
let appServices: IAppServices | null = null;
|
|
@@ -52,7 +52,7 @@ export abstract class BaseExecutor<TRequest, TResult, TOutput> {
|
|
|
52
52
|
onProgress?: (progress: number) => void,
|
|
53
53
|
): Promise<unknown>;
|
|
54
54
|
protected abstract validateExtractedResult(extracted: TOutput | undefined): string | undefined;
|
|
55
|
-
protected abstract transformResult(extracted: TOutput): TResult;
|
|
55
|
+
protected abstract transformResult(extracted: TOutput, request: TRequest): TResult;
|
|
56
56
|
protected abstract getDefaultExtractor(): (result: unknown) => TOutput | undefined;
|
|
57
57
|
|
|
58
58
|
private getProvider(): { provider: IAIProvider; error: null } | { provider: null; error: string } {
|
|
@@ -84,7 +84,7 @@ export abstract class BaseExecutor<TRequest, TResult, TOutput> {
|
|
|
84
84
|
return failure(error);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
return success(this.transformResult(extracted as TOutput));
|
|
87
|
+
return success(this.transformResult(extracted as TOutput, request));
|
|
88
88
|
}
|
|
89
89
|
|
|
90
90
|
protected log(level: "info" | "error", message: string): void {
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generation Orchestrator - Main Implementation
|
|
3
|
+
* Handles AI generation execution with network check, moderation, credit deduction, and error handling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useCallback, useState, useRef } from "react";
|
|
7
|
+
import type {
|
|
8
|
+
GenerationStatus,
|
|
9
|
+
GenerationState,
|
|
10
|
+
GenerationError,
|
|
11
|
+
GenerationStrategy,
|
|
12
|
+
GenerationConfig,
|
|
13
|
+
UseGenerationOrchestratorReturn,
|
|
14
|
+
} from "./orchestrator-types";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Main generation orchestrator hook
|
|
18
|
+
* Manages the complete generation lifecycle with error handling and state management
|
|
19
|
+
*/
|
|
20
|
+
export function useGenerationOrchestrator<TInput, TResult>(
|
|
21
|
+
strategy: GenerationStrategy<TInput, TResult>,
|
|
22
|
+
config: GenerationConfig,
|
|
23
|
+
): UseGenerationOrchestratorReturn<TInput, TResult> {
|
|
24
|
+
const [status, setStatus] = useState<GenerationStatus>("idle");
|
|
25
|
+
const [result, setResult] = useState<TResult | null>(null);
|
|
26
|
+
const [error, setError] = useState<GenerationError | null>(null);
|
|
27
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
28
|
+
|
|
29
|
+
const isGenerating = status === "checking" || status === "generating" || status === "saving";
|
|
30
|
+
|
|
31
|
+
const reset = useCallback(() => {
|
|
32
|
+
setStatus("idle");
|
|
33
|
+
setResult(null);
|
|
34
|
+
setError(null);
|
|
35
|
+
abortControllerRef.current?.abort();
|
|
36
|
+
abortControllerRef.current = null;
|
|
37
|
+
}, []);
|
|
38
|
+
|
|
39
|
+
const generate = useCallback(
|
|
40
|
+
async (input: TInput): Promise<TResult | void> => {
|
|
41
|
+
// Reset state
|
|
42
|
+
reset();
|
|
43
|
+
|
|
44
|
+
// Create abort controller for this generation
|
|
45
|
+
const abortController = new AbortController();
|
|
46
|
+
abortControllerRef.current = abortController;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
// Call lifecycle start hook
|
|
50
|
+
await config.lifecycle?.onStart?.();
|
|
51
|
+
|
|
52
|
+
// Set checking status
|
|
53
|
+
setStatus("checking");
|
|
54
|
+
|
|
55
|
+
// Execute generation
|
|
56
|
+
setStatus("generating");
|
|
57
|
+
const generationResult = await strategy.execute(input, abortController.signal);
|
|
58
|
+
|
|
59
|
+
// Save result if save strategy provided
|
|
60
|
+
if (strategy.save && config.userId) {
|
|
61
|
+
setStatus("saving");
|
|
62
|
+
await strategy.save(generationResult, config.userId);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Success
|
|
66
|
+
setStatus("success");
|
|
67
|
+
setResult(generationResult);
|
|
68
|
+
|
|
69
|
+
// Call success callbacks
|
|
70
|
+
await config.onSuccess?.(generationResult);
|
|
71
|
+
await config.lifecycle?.onComplete?.("success", generationResult);
|
|
72
|
+
|
|
73
|
+
// Show success alert if provided
|
|
74
|
+
if (config.alertMessages?.success) {
|
|
75
|
+
// Alert would be shown here in a real implementation
|
|
76
|
+
console.log(config.alertMessages.success);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return generationResult;
|
|
80
|
+
} catch (err) {
|
|
81
|
+
// Determine error type and create error object
|
|
82
|
+
let errorType: GenerationError["type"] = "unknown";
|
|
83
|
+
let errorMessage = "An error occurred during generation";
|
|
84
|
+
|
|
85
|
+
if (err instanceof Error) {
|
|
86
|
+
if (err.message.includes("network") || err.message.includes("fetch")) {
|
|
87
|
+
errorType = "network";
|
|
88
|
+
errorMessage = config.alertMessages?.networkError || err.message;
|
|
89
|
+
} else if (err.message.includes("moderation") || err.message.includes("content")) {
|
|
90
|
+
errorType = "moderation";
|
|
91
|
+
errorMessage = config.alertMessages?.moderationError || err.message;
|
|
92
|
+
} else if (err.message.includes("credits") || err.message.includes("insufficient")) {
|
|
93
|
+
errorType = "credits";
|
|
94
|
+
errorMessage = config.alertMessages?.insufficientCredits || err.message;
|
|
95
|
+
} else if (err.message.includes("save") || err.message.includes("storage")) {
|
|
96
|
+
errorType = "save";
|
|
97
|
+
errorMessage = config.alertMessages?.saveFailed || err.message;
|
|
98
|
+
} else {
|
|
99
|
+
errorType = "generation";
|
|
100
|
+
errorMessage = config.alertMessages?.generationError || err.message;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const generationError: GenerationError = {
|
|
105
|
+
type: errorType,
|
|
106
|
+
message: errorMessage,
|
|
107
|
+
originalError: err instanceof Error ? err : undefined,
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
setStatus("error");
|
|
111
|
+
setError(generationError);
|
|
112
|
+
|
|
113
|
+
// Call error callbacks
|
|
114
|
+
await config.onError?.(generationError);
|
|
115
|
+
await config.lifecycle?.onComplete?.("error", undefined, generationError);
|
|
116
|
+
} finally {
|
|
117
|
+
abortControllerRef.current = null;
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
[strategy, config, reset],
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
generate,
|
|
125
|
+
reset,
|
|
126
|
+
status,
|
|
127
|
+
isGenerating,
|
|
128
|
+
result,
|
|
129
|
+
error,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -71,8 +71,8 @@ export const useDualImageGeneration = (
|
|
|
71
71
|
const orchestrator = useGenerationOrchestrator(strategy, {
|
|
72
72
|
userId,
|
|
73
73
|
alertMessages,
|
|
74
|
-
onSuccess: (result) => onSuccess?.(result as string),
|
|
75
|
-
onError: (error) => onError?.(error.message),
|
|
74
|
+
onSuccess: async (result) => onSuccess?.(result as string),
|
|
75
|
+
onError: async (error) => onError?.(error.message),
|
|
76
76
|
});
|
|
77
77
|
|
|
78
78
|
// Process handler
|
|
@@ -88,7 +88,7 @@ export const useImageGeneration = <TInput extends ImageGenerationInput, TResult>
|
|
|
88
88
|
return useGenerationOrchestrator(strategy, {
|
|
89
89
|
userId,
|
|
90
90
|
alertMessages,
|
|
91
|
-
onSuccess: onSuccess
|
|
92
|
-
onError: handleError,
|
|
91
|
+
onSuccess: onSuccess ? async (result) => onSuccess(result) : undefined,
|
|
92
|
+
onError: async (error) => handleError(error),
|
|
93
93
|
});
|
|
94
94
|
};
|
|
@@ -103,7 +103,7 @@ export const useVideoGeneration = <TResult>(
|
|
|
103
103
|
return useGenerationOrchestrator(strategy, {
|
|
104
104
|
userId,
|
|
105
105
|
alertMessages,
|
|
106
|
-
onSuccess: onSuccess
|
|
107
|
-
onError: handleError,
|
|
106
|
+
onSuccess: onSuccess ? async (result) => onSuccess(result) : undefined,
|
|
107
|
+
onError: async (error) => handleError(error),
|
|
108
108
|
});
|
|
109
109
|
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic Generation Hook Factory - Main Implementation
|
|
3
|
+
* Creates type-safe generation hooks with error handling, progress tracking, and abort support
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useRef, useCallback, useEffect } from "react";
|
|
7
|
+
import type {
|
|
8
|
+
GenerationState,
|
|
9
|
+
GenerationCallbacks,
|
|
10
|
+
GenerationHookConfig,
|
|
11
|
+
GenerationHookReturn,
|
|
12
|
+
} from "./generation-hook-types";
|
|
13
|
+
import {
|
|
14
|
+
INITIAL_STATE,
|
|
15
|
+
createAbortHandler,
|
|
16
|
+
createProgressSetter,
|
|
17
|
+
createErrorSetter,
|
|
18
|
+
createCleanupEffect,
|
|
19
|
+
createCallbackUpdater,
|
|
20
|
+
} from "./generation-hook-utils";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a generation hook with standard configuration
|
|
24
|
+
*/
|
|
25
|
+
export function createGenerationHook<TRequest, TResult>(
|
|
26
|
+
defaultConfig: GenerationHookConfig<TRequest, TResult>) {
|
|
27
|
+
return function useGenerationHook(
|
|
28
|
+
request: TRequest | null,
|
|
29
|
+
callbacks?: GenerationCallbacks<TResult>,
|
|
30
|
+
configOverrides?: Partial<GenerationHookConfig<TRequest, TResult>>
|
|
31
|
+
): GenerationHookReturn<TRequest, TResult> {
|
|
32
|
+
const config = { ...defaultConfig, ...configOverrides };
|
|
33
|
+
const [generationState, setGenerationState] = useState<GenerationState>(INITIAL_STATE);
|
|
34
|
+
|
|
35
|
+
const abortControllerRef = useRef<AbortController | null>(null);
|
|
36
|
+
const isMountedRef = useRef(true);
|
|
37
|
+
|
|
38
|
+
const onSuccessRef = useRef(callbacks?.onSuccess);
|
|
39
|
+
const onErrorRef = useRef(callbacks?.onError);
|
|
40
|
+
const onProgressRef = useRef(callbacks?.onProgress);
|
|
41
|
+
|
|
42
|
+
// Update callbacks when they change
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
createCallbackUpdater(onSuccessRef, onErrorRef, onProgressRef, callbacks || {});
|
|
45
|
+
}, [callbacks]);
|
|
46
|
+
|
|
47
|
+
// Cleanup on unmount
|
|
48
|
+
useEffect(() => {
|
|
49
|
+
return createCleanupEffect(isMountedRef, abortControllerRef);
|
|
50
|
+
}, []);
|
|
51
|
+
|
|
52
|
+
const setProgress = createProgressSetter(
|
|
53
|
+
onProgressRef,
|
|
54
|
+
setGenerationState,
|
|
55
|
+
isMountedRef
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const setError = createErrorSetter(
|
|
59
|
+
onErrorRef,
|
|
60
|
+
setGenerationState,
|
|
61
|
+
isMountedRef
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const abort = createAbortHandler(abortControllerRef, setGenerationState);
|
|
65
|
+
|
|
66
|
+
const handleGenerate = useCallback(
|
|
67
|
+
async (req: TRequest): Promise<TResult | null> => {
|
|
68
|
+
// Validate if validator provided
|
|
69
|
+
if (config.validate) {
|
|
70
|
+
const validationError = config.validate(req);
|
|
71
|
+
if (validationError) {
|
|
72
|
+
setError(validationError);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
setGenerationState((prev) => ({
|
|
78
|
+
...prev,
|
|
79
|
+
isGenerating: true,
|
|
80
|
+
error: null,
|
|
81
|
+
progress: 0,
|
|
82
|
+
}));
|
|
83
|
+
|
|
84
|
+
try {
|
|
85
|
+
const abortController = new AbortController();
|
|
86
|
+
abortControllerRef.current = abortController;
|
|
87
|
+
|
|
88
|
+
const result = await config.execute(req, abortController.signal);
|
|
89
|
+
|
|
90
|
+
if (isMountedRef.current) {
|
|
91
|
+
setGenerationState((prev) => ({
|
|
92
|
+
...prev,
|
|
93
|
+
isGenerating: false,
|
|
94
|
+
progress: 100,
|
|
95
|
+
}));
|
|
96
|
+
onSuccessRef.current?.(result);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return result;
|
|
100
|
+
} catch (err) {
|
|
101
|
+
if (!abortControllerRef.current?.signal.aborted && isMountedRef.current) {
|
|
102
|
+
const errorMessage = config.transformError
|
|
103
|
+
? config.transformError(err)
|
|
104
|
+
: err instanceof Error
|
|
105
|
+
? err.message
|
|
106
|
+
: "Generation failed";
|
|
107
|
+
setError(errorMessage);
|
|
108
|
+
}
|
|
109
|
+
return null;
|
|
110
|
+
} finally {
|
|
111
|
+
abortControllerRef.current = null;
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
[config, setError]
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
return {
|
|
118
|
+
generationState,
|
|
119
|
+
handleGenerate,
|
|
120
|
+
setProgress,
|
|
121
|
+
setError,
|
|
122
|
+
abort,
|
|
123
|
+
};
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Creates a generation hook with built-in progress tracking
|
|
129
|
+
*/
|
|
130
|
+
export function createGenerationHookWithProgress<TRequest, TResult>(
|
|
131
|
+
defaultConfig: GenerationHookConfig<TRequest, TResult>
|
|
132
|
+
) {
|
|
133
|
+
return function useGenerationWithProgress(
|
|
134
|
+
request: TRequest | null,
|
|
135
|
+
callbacks?: GenerationCallbacks<TResult>,
|
|
136
|
+
configOverrides?: Partial<GenerationHookConfig<TRequest, TResult>>
|
|
137
|
+
): GenerationHookReturn<TRequest, TResult> {
|
|
138
|
+
// Reuse the standard hook
|
|
139
|
+
const useStandardHook = createGenerationHook(defaultConfig);
|
|
140
|
+
return useStandardHook(request, callbacks, configOverrides);
|
|
141
|
+
};
|
|
142
|
+
}
|
|
@@ -2,13 +2,19 @@
|
|
|
2
2
|
* Shared Validation Utilities
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
export type {
|
|
5
|
+
import type {
|
|
7
6
|
ValidationResult,
|
|
8
7
|
StringValidationOptions,
|
|
9
8
|
NumericValidationOptions,
|
|
10
9
|
} from "../../../infrastructure/validation/base-validator.types";
|
|
11
10
|
|
|
11
|
+
// Re-export types
|
|
12
|
+
export type {
|
|
13
|
+
ValidationResult,
|
|
14
|
+
StringValidationOptions,
|
|
15
|
+
NumericValidationOptions,
|
|
16
|
+
};
|
|
17
|
+
|
|
12
18
|
// Export functions from advanced-validator
|
|
13
19
|
export { combineValidationResults } from "../../../infrastructure/validation/advanced-validator";
|
|
14
20
|
|
|
File without changes
|