@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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/src/core/types/error.types.ts +3 -30
  3. package/src/core/types/provider.types.ts +35 -188
  4. package/src/core/types/result.types.ts +12 -96
  5. package/src/domain/entities/flow-config.types.ts +2 -2
  6. package/src/domains/generation/domain/generation.types.ts +4 -7
  7. package/src/domains/generation/index.ts +1 -1
  8. package/src/domains/generation/presentation/useAIGeneration.hook.ts +3 -15
  9. package/src/domains/generation/wizard/presentation/hooks/usePhotoBlockingGeneration.ts +1 -1
  10. package/src/features/image-to-video/infrastructure/services/image-to-video-executor.ts +10 -37
  11. package/src/features/image-to-video/presentation/hooks/image-to-video-feature.types.ts +2 -12
  12. package/src/features/image-to-video/presentation/hooks/useImageToVideoFeature.ts +24 -13
  13. package/src/features/text-to-video/infrastructure/services/text-to-video-executor.ts +5 -37
  14. package/src/features/text-to-video/presentation/hooks/useTextToVideoFeature.ts +26 -26
  15. package/src/infrastructure/orchestration/GenerationOrchestrator.ts +17 -14
  16. package/src/infrastructure/orchestration/orchestrator.types.ts +2 -5
  17. package/src/infrastructure/services/generation-orchestrator.service.ts +6 -6
  18. package/src/infrastructure/utils/README.md +7 -7
  19. package/src/infrastructure/utils/base64.util.ts +15 -0
  20. package/src/infrastructure/utils/error-classifier.util.ts +27 -10
  21. package/src/infrastructure/utils/error-message-extractor.util.ts +3 -3
  22. package/src/infrastructure/utils/error-patterns.constants.ts +21 -8
  23. package/src/infrastructure/utils/id-generator.util.ts +11 -0
  24. package/src/infrastructure/utils/index.ts +3 -0
  25. package/src/infrastructure/utils/provider-validator.util.ts +2 -2
  26. package/src/infrastructure/utils/video-result-extractor.util.ts +38 -0
  27. package/src/presentation/constants/alert-messages.ts +14 -0
  28. package/src/presentation/hooks/generation/errors.ts +5 -1
  29. package/src/presentation/hooks/generation/useImageGeneration.ts +6 -2
  30. package/src/presentation/hooks/generation/useVideoGeneration.ts +6 -2
  31. package/src/presentation/hooks/useGenerationFlow.ts +18 -23
  32. package/src/presentation/layouts/DualImageFeatureLayout.tsx +0 -1
  33. package/src/presentation/layouts/DualImageVideoFeatureLayout.tsx +1 -1
  34. package/src/presentation/layouts/SingleImageFeatureLayout.tsx +1 -1
  35. 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 = useMemo(() => ({ value: "" }), []);
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.value = input.creationId;
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: result.videoUrl ?? null,
73
- thumbnailUrl: result.thumbnailUrl ?? null,
82
+ videoUrl: videoResult.videoUrl ?? null,
83
+ thumbnailUrl: videoResult.thumbnailUrl ?? null,
74
84
  }));
75
85
 
76
- return result;
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.value) {
90
+ if (result.success && result.videoUrl && state.imageUri && currentCreationIdRef.current) {
81
91
  await callbacks?.onCreationSave?.({
82
- creationId: currentCreationIdRef.value,
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: state.videoUrl || undefined };
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, state.videoUrl, config, callbacks, orchestrator],
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 defaultExtractResult;
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 = useMemo(() => ({ value: "" }), []);
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.value = input.creationId;
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: result.videoUrl ?? null,
111
- thumbnailUrl: result.thumbnailUrl ?? null,
109
+ videoUrl: videoResult.videoUrl ?? null,
110
+ thumbnailUrl: videoResult.thumbnailUrl ?? null,
112
111
  }));
113
112
 
114
- return result;
113
+ return videoResult;
115
114
  },
116
115
  getCreditCost: () => config.creditCost,
117
116
  save: async (result) => {
118
- if (result.success && result.videoUrl && currentCreationIdRef.value) {
117
+ if (result.success && result.videoUrl && currentCreationIdRef.current) {
119
118
  await callbacks.onCreationSave?.({
120
- creationId: currentCreationIdRef.value,
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: state.videoUrl || undefined };
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, state.videoUrl, orchestrator],
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] 🔄 Refund check:", {
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
- const actualUserId = this.requireAuthenticatedUser();
149
-
150
- if (__DEV__) {
151
- console.log("[GenerationOrchestrator] Refunding credits:", {
152
- userId: actualUserId,
153
- amount,
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
- export type GenerationCapability =
34
- | "text-to-image"
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.INSUFFICIENT_CREDITS:
34
- // Handle insufficient credits
35
- showPaywall();
33
+ case AIErrorType.AUTHENTICATION:
34
+ // Handle authentication error
35
+ showLoginScreen();
36
36
  break;
37
- case AIErrorType.PROVIDER_ERROR:
38
- // Handle provider error
39
- showErrorMessage('Provider error occurred');
37
+ case AIErrorType.SERVER:
38
+ // Handle server error
39
+ showErrorMessage('Server error occurred');
40
40
  break;
41
- case AIErrorType.NETWORK_ERROR:
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
- return (
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
- (errorName === "apierror" && retryAttempt < maxRetries - 1)
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
- "403",
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
- "502",
41
- "503",
42
- "504",
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" };