@umituz/react-native-ai-generation-content 1.61.25 → 1.61.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/core/types/error.types.ts +3 -30
- package/src/core/types/provider.types.ts +35 -188
- package/src/core/types/result.types.ts +12 -96
- package/src/domain/entities/flow-config.types.ts +2 -2
- package/src/domains/generation/domain/generation.types.ts +4 -7
- package/src/domains/generation/index.ts +1 -1
- package/src/domains/generation/presentation/useAIGeneration.hook.ts +3 -15
- package/src/domains/generation/wizard/presentation/hooks/usePhotoBlockingGeneration.ts +1 -1
- package/src/features/image-to-video/infrastructure/services/image-to-video-executor.ts +10 -37
- package/src/features/image-to-video/presentation/hooks/image-to-video-feature.types.ts +2 -12
- package/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.ts +24 -13
- package/src/features/text-to-video/infrastructure/services/text-to-video-executor.ts +5 -37
- package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +26 -26
- package/src/infrastructure/orchestration/GenerationOrchestrator.ts +17 -14
- package/src/infrastructure/orchestration/orchestrator.types.ts +2 -5
- package/src/infrastructure/services/generation-orchestrator.service.ts +6 -6
- package/src/infrastructure/utils/README.md +7 -7
- package/src/infrastructure/utils/base64.util.ts +15 -0
- package/src/infrastructure/utils/error-classifier.util.ts +27 -10
- package/src/infrastructure/utils/error-message-extractor.util.ts +3 -3
- package/src/infrastructure/utils/error-patterns.constants.ts +21 -8
- package/src/infrastructure/utils/id-generator.util.ts +11 -0
- package/src/infrastructure/utils/index.ts +3 -0
- package/src/infrastructure/utils/provider-validator.util.ts +2 -2
- package/src/infrastructure/utils/video-result-extractor.util.ts +38 -0
- package/src/presentation/constants/alert-messages.ts +14 -0
- package/src/presentation/hooks/generation/errors.ts +5 -1
- package/src/presentation/hooks/generation/useImageGeneration.ts +6 -2
- package/src/presentation/hooks/generation/useVideoGeneration.ts +6 -2
- package/src/presentation/hooks/useGenerationFlow.ts +18 -23
- package/src/presentation/layouts/DualImageFeatureLayout.tsx +0 -1
- package/src/presentation/layouts/DualImageVideoFeatureLayout.tsx +1 -1
- package/src/presentation/layouts/SingleImageFeatureLayout.tsx +1 -1
- package/src/presentation/layouts/SingleImageWithPromptFeatureLayout.tsx +1 -1
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Uses centralized useGenerationOrchestrator for consistent auth, credits, and error handling
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useState, useCallback, useMemo } from "react";
|
|
6
|
+
import { useState, useCallback, useMemo, useRef } from "react";
|
|
7
7
|
import {
|
|
8
8
|
useGenerationOrchestrator,
|
|
9
9
|
type GenerationStrategy,
|
|
@@ -29,7 +29,7 @@ export type {
|
|
|
29
29
|
export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseImageToVideoFeatureReturn {
|
|
30
30
|
const { config, callbacks, userId } = props;
|
|
31
31
|
const [state, setState] = useState(INITIAL_STATE);
|
|
32
|
-
const currentCreationIdRef =
|
|
32
|
+
const currentCreationIdRef = useRef("");
|
|
33
33
|
|
|
34
34
|
const strategy: GenerationStrategy<VideoGenerationInput, ImageToVideoResult> = useMemo(
|
|
35
35
|
() => ({
|
|
@@ -38,7 +38,7 @@ export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseI
|
|
|
38
38
|
console.log("[ImageToVideo] Executing generation, creationId:", input.creationId);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
currentCreationIdRef.
|
|
41
|
+
currentCreationIdRef.current = input.creationId;
|
|
42
42
|
config.onProcessingStart?.();
|
|
43
43
|
|
|
44
44
|
callbacks?.onGenerationStart?.({
|
|
@@ -46,7 +46,11 @@ export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseI
|
|
|
46
46
|
type: "image-to-video",
|
|
47
47
|
imageUri: input.imageUri,
|
|
48
48
|
metadata: input.options as Record<string, unknown> | undefined,
|
|
49
|
-
}).catch(() => {
|
|
49
|
+
}).catch((err) => {
|
|
50
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
51
|
+
console.warn("[ImageToVideo] onGenerationStart failed:", err);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
50
54
|
|
|
51
55
|
const result = await executeImageToVideo(
|
|
52
56
|
{
|
|
@@ -67,19 +71,25 @@ export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseI
|
|
|
67
71
|
throw new Error(result.error || "Generation failed");
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
const videoResult: ImageToVideoResult = {
|
|
75
|
+
success: true,
|
|
76
|
+
videoUrl: result.videoUrl,
|
|
77
|
+
thumbnailUrl: result.thumbnailUrl,
|
|
78
|
+
};
|
|
79
|
+
|
|
70
80
|
setState((prev) => ({
|
|
71
81
|
...prev,
|
|
72
|
-
videoUrl:
|
|
73
|
-
thumbnailUrl:
|
|
82
|
+
videoUrl: videoResult.videoUrl ?? null,
|
|
83
|
+
thumbnailUrl: videoResult.thumbnailUrl ?? null,
|
|
74
84
|
}));
|
|
75
85
|
|
|
76
|
-
return
|
|
86
|
+
return videoResult;
|
|
77
87
|
},
|
|
78
88
|
getCreditCost: () => config.creditCost ?? 0,
|
|
79
89
|
save: async (result) => {
|
|
80
|
-
if (result.success && result.videoUrl && state.imageUri && currentCreationIdRef.
|
|
90
|
+
if (result.success && result.videoUrl && state.imageUri && currentCreationIdRef.current) {
|
|
81
91
|
await callbacks?.onCreationSave?.({
|
|
82
|
-
creationId: currentCreationIdRef.
|
|
92
|
+
creationId: currentCreationIdRef.current,
|
|
83
93
|
type: "image-to-video",
|
|
84
94
|
videoUrl: result.videoUrl,
|
|
85
95
|
thumbnailUrl: result.thumbnailUrl,
|
|
@@ -149,19 +159,20 @@ export function useImageToVideoFeature(props: UseImageToVideoFeatureProps): UseI
|
|
|
149
159
|
imageBase64,
|
|
150
160
|
motionPrompt: effectiveMotionPrompt,
|
|
151
161
|
options,
|
|
152
|
-
creationId: generateCreationId(),
|
|
162
|
+
creationId: generateCreationId("image-to-video"),
|
|
153
163
|
};
|
|
154
164
|
|
|
155
|
-
await orchestrator.generate(input);
|
|
165
|
+
const genResult = await orchestrator.generate(input);
|
|
166
|
+
const videoResult = genResult as ImageToVideoResult | undefined;
|
|
156
167
|
setState((prev) => ({ ...prev, isProcessing: false }));
|
|
157
|
-
return { success: true, videoUrl:
|
|
168
|
+
return { success: true, videoUrl: videoResult?.videoUrl || undefined };
|
|
158
169
|
} catch (error) {
|
|
159
170
|
const message = error instanceof Error ? error.message : "Generation failed";
|
|
160
171
|
setState((prev) => ({ ...prev, isProcessing: false, error: message }));
|
|
161
172
|
return { success: false, error: message };
|
|
162
173
|
}
|
|
163
174
|
},
|
|
164
|
-
[state.imageUri, state.motionPrompt,
|
|
175
|
+
[state.imageUri, state.motionPrompt, config, callbacks, orchestrator],
|
|
165
176
|
);
|
|
166
177
|
|
|
167
178
|
const reset = useCallback(() => {
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
|
|
6
6
|
import { BaseExecutor } from "../../../../infrastructure/executors/base-executor";
|
|
7
7
|
import { isSuccess, type Result } from "../../../../domain/types/result.types";
|
|
8
|
+
import {
|
|
9
|
+
defaultExtractVideoResult,
|
|
10
|
+
type ExtractedVideoResult,
|
|
11
|
+
} from "../../../../infrastructure/utils/video-result-extractor.util";
|
|
8
12
|
import type { IAIProvider } from "../../../../domain/interfaces";
|
|
9
13
|
import type {
|
|
10
14
|
TextToVideoRequest,
|
|
@@ -25,42 +29,6 @@ export interface ExecuteTextToVideoOptions {
|
|
|
25
29
|
onProgress?: (progress: number) => void;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
/**
|
|
29
|
-
* Extracted result structure from provider response
|
|
30
|
-
*/
|
|
31
|
-
interface ExtractedVideoResult {
|
|
32
|
-
videoUrl?: string;
|
|
33
|
-
thumbnailUrl?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Default extractor for text-to-video results
|
|
38
|
-
*/
|
|
39
|
-
function defaultExtractResult(
|
|
40
|
-
result: unknown,
|
|
41
|
-
): ExtractedVideoResult | undefined {
|
|
42
|
-
if (typeof result !== "object" || result === null) return undefined;
|
|
43
|
-
|
|
44
|
-
const r = result as Record<string, unknown>;
|
|
45
|
-
|
|
46
|
-
if (typeof r.video === "string") {
|
|
47
|
-
return { videoUrl: r.video };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (r.video && typeof r.video === "object") {
|
|
51
|
-
const video = r.video as Record<string, unknown>;
|
|
52
|
-
if (typeof video.url === "string") {
|
|
53
|
-
return {
|
|
54
|
-
videoUrl: video.url,
|
|
55
|
-
thumbnailUrl:
|
|
56
|
-
typeof r.thumbnail === "string" ? r.thumbnail : undefined,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return undefined;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
32
|
/**
|
|
65
33
|
* Text-to-Video Executor using Template Method pattern
|
|
66
34
|
* Eliminates code duplication through BaseExecutor
|
|
@@ -126,7 +94,7 @@ class TextToVideoExecutor extends BaseExecutor<
|
|
|
126
94
|
protected getDefaultExtractor(): (
|
|
127
95
|
result: unknown,
|
|
128
96
|
) => ExtractedVideoResult | undefined {
|
|
129
|
-
return
|
|
97
|
+
return defaultExtractVideoResult;
|
|
130
98
|
}
|
|
131
99
|
}
|
|
132
100
|
|
|
@@ -3,12 +3,13 @@
|
|
|
3
3
|
* Simplified hook for text-to-video generation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { useState, useCallback, useMemo } from "react";
|
|
6
|
+
import { useState, useCallback, useMemo, useRef } from "react";
|
|
7
7
|
import {
|
|
8
8
|
useGenerationOrchestrator,
|
|
9
9
|
type GenerationStrategy,
|
|
10
|
-
type AlertMessages,
|
|
11
10
|
} from "../../../../presentation/hooks/generation";
|
|
11
|
+
import { DEFAULT_ALERT_MESSAGES } from "../../../../presentation/constants/alert-messages";
|
|
12
|
+
import { generateCreationId } from "../../../../infrastructure/utils/id-generator.util";
|
|
12
13
|
import { executeTextToVideo } from "../../infrastructure/services";
|
|
13
14
|
import type {
|
|
14
15
|
TextToVideoFeatureState,
|
|
@@ -52,29 +53,17 @@ const INITIAL_STATE: TextToVideoFeatureState = {
|
|
|
52
53
|
error: null,
|
|
53
54
|
};
|
|
54
55
|
|
|
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
56
|
interface VideoGenerationInput {
|
|
64
57
|
prompt: string;
|
|
65
58
|
options?: TextToVideoOptions;
|
|
66
59
|
creationId: string;
|
|
67
60
|
}
|
|
68
61
|
|
|
69
|
-
function generateCreationId(): string {
|
|
70
|
-
return `text-to-video_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
62
|
export function useTextToVideoFeature(props: UseTextToVideoFeatureProps): UseTextToVideoFeatureReturn {
|
|
74
63
|
const { config, callbacks, userId, buildInput, extractResult } = props;
|
|
75
64
|
const [state, setState] = useState<TextToVideoFeatureState>(INITIAL_STATE);
|
|
76
65
|
|
|
77
|
-
const currentCreationIdRef =
|
|
66
|
+
const currentCreationIdRef = useRef("");
|
|
78
67
|
|
|
79
68
|
const strategy: GenerationStrategy<VideoGenerationInput, TextToVideoResult> = useMemo(
|
|
80
69
|
() => ({
|
|
@@ -83,14 +72,18 @@ export function useTextToVideoFeature(props: UseTextToVideoFeatureProps): UseTex
|
|
|
83
72
|
console.log("[TextToVideo] Executing generation:", input.prompt.slice(0, 100));
|
|
84
73
|
}
|
|
85
74
|
|
|
86
|
-
currentCreationIdRef.
|
|
75
|
+
currentCreationIdRef.current = input.creationId;
|
|
87
76
|
|
|
88
77
|
callbacks.onGenerationStart?.({
|
|
89
78
|
creationId: input.creationId,
|
|
90
79
|
type: "text-to-video",
|
|
91
80
|
prompt: input.prompt,
|
|
92
81
|
metadata: input.options as Record<string, unknown> | undefined,
|
|
93
|
-
}).catch(() => {
|
|
82
|
+
}).catch((err) => {
|
|
83
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
84
|
+
console.warn("[TextToVideo] onGenerationStart failed:", err);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
94
87
|
|
|
95
88
|
const result = await executeTextToVideo(
|
|
96
89
|
{ prompt: input.prompt, userId, options: input.options },
|
|
@@ -105,19 +98,25 @@ export function useTextToVideoFeature(props: UseTextToVideoFeatureProps): UseTex
|
|
|
105
98
|
throw new Error(result.error || "Generation failed");
|
|
106
99
|
}
|
|
107
100
|
|
|
101
|
+
const videoResult: TextToVideoResult = {
|
|
102
|
+
success: true,
|
|
103
|
+
videoUrl: result.videoUrl,
|
|
104
|
+
thumbnailUrl: result.thumbnailUrl,
|
|
105
|
+
};
|
|
106
|
+
|
|
108
107
|
setState((prev) => ({
|
|
109
108
|
...prev,
|
|
110
|
-
videoUrl:
|
|
111
|
-
thumbnailUrl:
|
|
109
|
+
videoUrl: videoResult.videoUrl ?? null,
|
|
110
|
+
thumbnailUrl: videoResult.thumbnailUrl ?? null,
|
|
112
111
|
}));
|
|
113
112
|
|
|
114
|
-
return
|
|
113
|
+
return videoResult;
|
|
115
114
|
},
|
|
116
115
|
getCreditCost: () => config.creditCost,
|
|
117
116
|
save: async (result) => {
|
|
118
|
-
if (result.success && result.videoUrl && currentCreationIdRef.
|
|
117
|
+
if (result.success && result.videoUrl && currentCreationIdRef.current) {
|
|
119
118
|
await callbacks.onCreationSave?.({
|
|
120
|
-
creationId: currentCreationIdRef.
|
|
119
|
+
creationId: currentCreationIdRef.current,
|
|
121
120
|
type: "text-to-video",
|
|
122
121
|
videoUrl: result.videoUrl,
|
|
123
122
|
thumbnailUrl: result.thumbnailUrl,
|
|
@@ -161,18 +160,19 @@ export function useTextToVideoFeature(props: UseTextToVideoFeatureProps): UseTex
|
|
|
161
160
|
const input: VideoGenerationInput = {
|
|
162
161
|
prompt: prompt.trim(),
|
|
163
162
|
options: params,
|
|
164
|
-
creationId: generateCreationId(),
|
|
163
|
+
creationId: generateCreationId("text-to-video"),
|
|
165
164
|
};
|
|
166
|
-
await orchestrator.generate(input);
|
|
165
|
+
const result = await orchestrator.generate(input);
|
|
166
|
+
const videoResult = result as TextToVideoResult | undefined;
|
|
167
167
|
setState((prev) => ({ ...prev, isProcessing: false }));
|
|
168
|
-
return { success: true, videoUrl:
|
|
168
|
+
return { success: true, videoUrl: videoResult?.videoUrl || undefined };
|
|
169
169
|
} catch (error) {
|
|
170
170
|
const message = error instanceof Error ? error.message : "Generation failed";
|
|
171
171
|
setState((prev) => ({ ...prev, isProcessing: false, error: message }));
|
|
172
172
|
return { success: false, error: message };
|
|
173
173
|
}
|
|
174
174
|
},
|
|
175
|
-
[state.prompt,
|
|
175
|
+
[state.prompt, orchestrator],
|
|
176
176
|
);
|
|
177
177
|
|
|
178
178
|
const reset = useCallback(() => {
|
|
@@ -45,7 +45,6 @@ export class GenerationOrchestrator {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
async moderateContent(
|
|
48
|
-
_userId: string,
|
|
49
48
|
contentType: "text" | "image",
|
|
50
49
|
content: string,
|
|
51
50
|
metadata?: GenerationMetadata,
|
|
@@ -97,7 +96,6 @@ export class GenerationOrchestrator {
|
|
|
97
96
|
}
|
|
98
97
|
|
|
99
98
|
async processCredits(
|
|
100
|
-
_userId: string,
|
|
101
99
|
capability: GenerationCapability,
|
|
102
100
|
metadata?: GenerationMetadata,
|
|
103
101
|
): Promise<number> {
|
|
@@ -121,7 +119,6 @@ export class GenerationOrchestrator {
|
|
|
121
119
|
}
|
|
122
120
|
|
|
123
121
|
async refundCreditsIfApplicable(
|
|
124
|
-
_userId: string,
|
|
125
122
|
amount: number,
|
|
126
123
|
error: unknown,
|
|
127
124
|
): Promise<void> {
|
|
@@ -135,8 +132,8 @@ export class GenerationOrchestrator {
|
|
|
135
132
|
error.message.toLowerCase().includes("cancelled") ||
|
|
136
133
|
error.message.toLowerCase().includes("user cancel")));
|
|
137
134
|
|
|
138
|
-
if (__DEV__) {
|
|
139
|
-
console.log("[GenerationOrchestrator]
|
|
135
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
136
|
+
console.log("[GenerationOrchestrator] Refund check:", {
|
|
140
137
|
amount,
|
|
141
138
|
isNonRefundable,
|
|
142
139
|
errorType: error instanceof Error ? error.name : typeof error,
|
|
@@ -145,16 +142,22 @@ export class GenerationOrchestrator {
|
|
|
145
142
|
}
|
|
146
143
|
|
|
147
144
|
if (!isNonRefundable) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
try {
|
|
146
|
+
const actualUserId = this.requireAuthenticatedUser();
|
|
147
|
+
|
|
148
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
149
|
+
console.log("[GenerationOrchestrator] Refunding credits:", {
|
|
150
|
+
userId: actualUserId,
|
|
151
|
+
amount,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
await this.config.creditService.add(actualUserId, amount);
|
|
156
|
+
} catch (refundErr) {
|
|
157
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
158
|
+
console.error("[GenerationOrchestrator] Refund failed:", refundErr);
|
|
159
|
+
}
|
|
155
160
|
}
|
|
156
|
-
|
|
157
|
-
await this.config.creditService.add(actualUserId, amount);
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
163
|
}
|
|
@@ -30,11 +30,8 @@ export interface GenerationMetadata {
|
|
|
30
30
|
[key: string]: unknown;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
| "text-to-video"
|
|
36
|
-
| "image-to-video"
|
|
37
|
-
| "text-to-voice";
|
|
33
|
+
import type { GenerationCapability } from "../../domain/entities/generation.types";
|
|
34
|
+
export type { GenerationCapability } from "../../domain/entities/generation.types";
|
|
38
35
|
|
|
39
36
|
export interface OrchestratorConfig {
|
|
40
37
|
creditService: CreditService;
|
|
@@ -26,7 +26,7 @@ class GenerationOrchestratorService {
|
|
|
26
26
|
private onStatusUpdateCallback?: (requestId: string, status: string) => Promise<void>;
|
|
27
27
|
|
|
28
28
|
configure(config: OrchestratorConfig): void {
|
|
29
|
-
if (__DEV__) {
|
|
29
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
30
30
|
console.log("[Orchestrator] configure() called", {
|
|
31
31
|
hasPollingConfig: !!config.polling,
|
|
32
32
|
hasStatusUpdate: !!config.onStatusUpdate,
|
|
@@ -43,7 +43,7 @@ class GenerationOrchestratorService {
|
|
|
43
43
|
const provider = this.providerValidator.getProvider();
|
|
44
44
|
const startTime = Date.now();
|
|
45
45
|
|
|
46
|
-
if (__DEV__) {
|
|
46
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
47
47
|
console.log("[Orchestrator] Generate started:", {
|
|
48
48
|
model: request.model,
|
|
49
49
|
capability: request.capability,
|
|
@@ -54,7 +54,7 @@ class GenerationOrchestratorService {
|
|
|
54
54
|
try {
|
|
55
55
|
const submission = await provider.submitJob(request.model, request.input);
|
|
56
56
|
|
|
57
|
-
if (__DEV__) {
|
|
57
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
58
58
|
console.log("[Orchestrator] Job submitted:", {
|
|
59
59
|
requestId: submission.requestId,
|
|
60
60
|
provider: provider.providerId,
|
|
@@ -82,13 +82,13 @@ class GenerationOrchestratorService {
|
|
|
82
82
|
});
|
|
83
83
|
|
|
84
84
|
if (!pollResult.success) {
|
|
85
|
-
throw pollResult.error;
|
|
85
|
+
throw pollResult.error ?? new Error("Polling failed");
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
const result = pollResult.data as T;
|
|
89
89
|
const duration = Date.now() - startTime;
|
|
90
90
|
|
|
91
|
-
if (__DEV__) {
|
|
91
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
92
92
|
console.log("[Orchestrator] Generate completed:", {
|
|
93
93
|
requestId: submission.requestId,
|
|
94
94
|
duration: `${duration}ms`,
|
|
@@ -112,7 +112,7 @@ class GenerationOrchestratorService {
|
|
|
112
112
|
} catch (error) {
|
|
113
113
|
const errorInfo = classifyError(error);
|
|
114
114
|
|
|
115
|
-
if (__DEV__) {
|
|
115
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
116
116
|
console.error("[Orchestrator] Generation failed:", errorInfo);
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -30,15 +30,15 @@ try {
|
|
|
30
30
|
const errorType = classifyError(error);
|
|
31
31
|
|
|
32
32
|
switch (errorType) {
|
|
33
|
-
case AIErrorType.
|
|
34
|
-
// Handle
|
|
35
|
-
|
|
33
|
+
case AIErrorType.AUTHENTICATION:
|
|
34
|
+
// Handle authentication error
|
|
35
|
+
showLoginScreen();
|
|
36
36
|
break;
|
|
37
|
-
case AIErrorType.
|
|
38
|
-
// Handle
|
|
39
|
-
showErrorMessage('
|
|
37
|
+
case AIErrorType.SERVER:
|
|
38
|
+
// Handle server error
|
|
39
|
+
showErrorMessage('Server error occurred');
|
|
40
40
|
break;
|
|
41
|
-
case AIErrorType.
|
|
41
|
+
case AIErrorType.NETWORK:
|
|
42
42
|
// Handle network error
|
|
43
43
|
showRetryOption();
|
|
44
44
|
break;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base64 Utility
|
|
3
|
+
* Shared base64 formatting functions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const BASE64_IMAGE_PREFIX = "data:image/jpeg;base64,";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Ensures a base64 string has the proper data URI prefix.
|
|
10
|
+
* If already prefixed, returns as-is.
|
|
11
|
+
*/
|
|
12
|
+
export function formatBase64(base64: string): string {
|
|
13
|
+
if (!base64) return "";
|
|
14
|
+
return base64.startsWith("data:") ? base64 : `${BASE64_IMAGE_PREFIX}${base64}`;
|
|
15
|
+
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
RATE_LIMIT_PATTERNS,
|
|
10
10
|
AUTH_ERROR_PATTERNS,
|
|
11
11
|
CONTENT_POLICY_PATTERNS,
|
|
12
|
+
VALIDATION_ERROR_PATTERNS,
|
|
12
13
|
SERVER_ERROR_PATTERNS,
|
|
13
14
|
} from "./error-patterns.constants";
|
|
14
15
|
|
|
@@ -33,7 +34,7 @@ function getStatusCode(error: unknown): number | undefined {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
function logClassification(info: AIErrorInfo): AIErrorInfo {
|
|
36
|
-
if (__DEV__) {
|
|
37
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
37
38
|
console.log("[ErrorClassifier] Classified as:", {
|
|
38
39
|
type: info.type,
|
|
39
40
|
messageKey: info.messageKey,
|
|
@@ -47,7 +48,7 @@ export function classifyError(error: unknown): AIErrorInfo {
|
|
|
47
48
|
const message = error instanceof Error ? error.message : String(error);
|
|
48
49
|
const statusCode = getStatusCode(error);
|
|
49
50
|
|
|
50
|
-
if (__DEV__) {
|
|
51
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
51
52
|
console.log("[ErrorClassifier] Classifying error:", {
|
|
52
53
|
message: message.slice(0, 100),
|
|
53
54
|
statusCode,
|
|
@@ -88,6 +89,16 @@ export function classifyError(error: unknown): AIErrorInfo {
|
|
|
88
89
|
});
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
if (matchesPatterns(message, VALIDATION_ERROR_PATTERNS)) {
|
|
93
|
+
return logClassification({
|
|
94
|
+
type: AIErrorType.VALIDATION,
|
|
95
|
+
messageKey: "error.validation",
|
|
96
|
+
retryable: false,
|
|
97
|
+
originalError: error,
|
|
98
|
+
statusCode,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
91
102
|
if (matchesPatterns(message, NETWORK_ERROR_PATTERNS)) {
|
|
92
103
|
return logClassification({
|
|
93
104
|
type: AIErrorType.NETWORK,
|
|
@@ -149,20 +160,26 @@ export function isResultNotReady(
|
|
|
149
160
|
retryAttempt: number,
|
|
150
161
|
maxRetries: number,
|
|
151
162
|
): boolean {
|
|
152
|
-
if (isPermanentError(error)) {
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
163
|
const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();
|
|
157
164
|
const errorName = error instanceof Error ? error.constructor.name.toLowerCase() : "";
|
|
158
165
|
|
|
159
|
-
|
|
166
|
+
// Check 404/not-found patterns first - these indicate the job is still processing
|
|
167
|
+
const isNotFoundPattern =
|
|
160
168
|
message.includes("not found") ||
|
|
161
169
|
message.includes("404") ||
|
|
162
170
|
message.includes("still in progress") ||
|
|
163
171
|
message.includes("result not ready") ||
|
|
164
172
|
message.includes("request is still in progress") ||
|
|
165
|
-
message.includes("job result not found")
|
|
166
|
-
|
|
167
|
-
)
|
|
173
|
+
message.includes("job result not found");
|
|
174
|
+
|
|
175
|
+
if (isNotFoundPattern) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Only then check for permanent errors
|
|
180
|
+
if (isPermanentError(error)) {
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return errorName === "apierror" && retryAttempt < maxRetries - 1;
|
|
168
185
|
}
|
|
@@ -67,7 +67,7 @@ export function checkFalApiError(result: unknown): void {
|
|
|
67
67
|
const errorType = firstError?.type || "unknown";
|
|
68
68
|
const errorMsg = firstError?.msg || "Unknown API error";
|
|
69
69
|
|
|
70
|
-
if (__DEV__) {
|
|
70
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
71
71
|
console.error("[FalApiError] Detected error in result:", {
|
|
72
72
|
type: errorType,
|
|
73
73
|
message: errorMsg,
|
|
@@ -138,7 +138,7 @@ export function extractErrorMessage(
|
|
|
138
138
|
// First check if this is a GenerationError with translation key
|
|
139
139
|
const translationKey = getErrorTranslationKey(error);
|
|
140
140
|
if (translationKey) {
|
|
141
|
-
if (__DEV__ && debugPrefix) {
|
|
141
|
+
if (typeof __DEV__ !== "undefined" && __DEV__ && debugPrefix) {
|
|
142
142
|
console.error(`[${debugPrefix}] Error (translation key):`, translationKey);
|
|
143
143
|
}
|
|
144
144
|
return translationKey;
|
|
@@ -168,7 +168,7 @@ export function extractErrorMessage(
|
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
-
if (__DEV__ && debugPrefix) {
|
|
171
|
+
if (typeof __DEV__ !== "undefined" && __DEV__ && debugPrefix) {
|
|
172
172
|
console.error(`[${debugPrefix}] Error:`, message, error);
|
|
173
173
|
}
|
|
174
174
|
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
|
|
6
6
|
export const NETWORK_ERROR_PATTERNS = [
|
|
7
7
|
"network",
|
|
8
|
-
"timeout",
|
|
9
8
|
"socket",
|
|
10
9
|
"econnrefused",
|
|
11
10
|
"enotfound",
|
|
@@ -19,8 +18,10 @@ export const AUTH_ERROR_PATTERNS = [
|
|
|
19
18
|
"unauthorized",
|
|
20
19
|
"authentication",
|
|
21
20
|
"invalid api key",
|
|
22
|
-
"401",
|
|
23
|
-
"
|
|
21
|
+
" 401",
|
|
22
|
+
"401 ",
|
|
23
|
+
" 403",
|
|
24
|
+
"403 ",
|
|
24
25
|
] as const;
|
|
25
26
|
|
|
26
27
|
export const CONTENT_POLICY_PATTERNS = [
|
|
@@ -30,15 +31,27 @@ export const CONTENT_POLICY_PATTERNS = [
|
|
|
30
31
|
"safety",
|
|
31
32
|
"moderation",
|
|
32
33
|
"inappropriate",
|
|
33
|
-
"blocked",
|
|
34
|
+
"content blocked",
|
|
35
|
+
"blocked by",
|
|
34
36
|
"flagged by a content checker",
|
|
35
37
|
] as const;
|
|
36
38
|
|
|
39
|
+
export const VALIDATION_ERROR_PATTERNS = [
|
|
40
|
+
"validation",
|
|
41
|
+
"invalid input",
|
|
42
|
+
"required field",
|
|
43
|
+
"invalid parameter",
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
37
46
|
export const SERVER_ERROR_PATTERNS = [
|
|
38
47
|
"internal server error",
|
|
39
|
-
"500",
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
48
|
+
" 500",
|
|
49
|
+
"500 ",
|
|
50
|
+
" 502",
|
|
51
|
+
"502 ",
|
|
52
|
+
" 503",
|
|
53
|
+
"503 ",
|
|
54
|
+
" 504",
|
|
55
|
+
"504 ",
|
|
43
56
|
"service unavailable",
|
|
44
57
|
] as const;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ID Generator Utility
|
|
3
|
+
* Shared creation ID generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate a unique creation ID with prefix.
|
|
8
|
+
*/
|
|
9
|
+
export function generateCreationId(prefix: string): string {
|
|
10
|
+
return `${prefix}_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
11
|
+
}
|
|
@@ -13,3 +13,6 @@ export * from "./photo-generation";
|
|
|
13
13
|
export * from "./feature-utils";
|
|
14
14
|
export * from "./video-helpers";
|
|
15
15
|
export * from "./provider-validator.util";
|
|
16
|
+
export * from "./base64.util";
|
|
17
|
+
export * from "./video-result-extractor.util";
|
|
18
|
+
export * from "./id-generator.util";
|
|
@@ -20,14 +20,14 @@ export function validateProvider(context: string): ProviderValidationResult {
|
|
|
20
20
|
const provider = providerRegistry.getActiveProvider();
|
|
21
21
|
|
|
22
22
|
if (!provider) {
|
|
23
|
-
if (__DEV__) {
|
|
23
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
24
24
|
console.log(`[${context}] ERROR: No provider`);
|
|
25
25
|
}
|
|
26
26
|
return { success: false, error: "No AI provider configured" };
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
if (!provider.isInitialized()) {
|
|
30
|
-
if (__DEV__) {
|
|
30
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
31
31
|
console.log(`[${context}] ERROR: Provider not initialized`);
|
|
32
32
|
}
|
|
33
33
|
return { success: false, error: "AI provider not initialized" };
|