@umituz/react-native-ai-generation-content 1.26.72 → 1.26.74

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.26.72",
3
+ "version": "1.26.74",
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",
@@ -46,11 +46,11 @@
46
46
  "@tanstack/react-query": ">=5.0.0",
47
47
  "@umituz/react-native-subscription": ">=2.23.0",
48
48
  "@umituz/react-native-video-editor": ">=1.0.0",
49
+ "expo-video": ">=1.0.0",
49
50
  "firebase": ">=10.0.0",
50
51
  "react": ">=18.0.0",
51
52
  "react-native": ">=0.74.0",
52
- "react-native-safe-area-context": ">=4.0.0",
53
- "expo-video": ">=1.0.0"
53
+ "react-native-safe-area-context": ">=4.0.0"
54
54
  },
55
55
  "devDependencies": {
56
56
  "@expo/vector-icons": "^15.0.3",
@@ -67,7 +67,7 @@
67
67
  "@types/react": "~19.1.10",
68
68
  "@typescript-eslint/eslint-plugin": "^8.0.0",
69
69
  "@typescript-eslint/parser": "^8.0.0",
70
- "@umituz/react-native-design-system": "*",
70
+ "@umituz/react-native-design-system": "^2.9.44",
71
71
  "@umituz/react-native-firebase": "*",
72
72
  "@umituz/react-native-localization": "*",
73
73
  "@umituz/react-native-subscription": "*",
@@ -99,6 +99,7 @@
99
99
  "react-i18next": "^16.5.0",
100
100
  "react-native": "0.81.5",
101
101
  "react-native-gesture-handler": "^2.30.0",
102
+ "react-native-purchases": "^9.7.1",
102
103
  "react-native-safe-area-context": "^5.6.2",
103
104
  "react-native-svg": "^15.15.1",
104
105
  "rn-emoji-keyboard": "^1.7.0",
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { getMediaTypeFromUrl, extractMediaUrl } from "@umituz/react-native-design-system";
2
+ import { extractMediaUrl, getMediaTypeFromUrl } from "@umituz/react-native-design-system";
3
3
  import { StepType } from "../../../../../domain/entities/flow-config.types";
4
4
  import { GenericPhotoUploadScreen } from "../screens/GenericPhotoUploadScreen";
5
5
  import { GeneratingScreen } from "../screens/GeneratingScreen";
@@ -6,7 +6,7 @@
6
6
 
7
7
  import { useState, useCallback, useEffect } from "react";
8
8
  import { Alert } from "react-native";
9
- import { useMedia, MediaValidationError, MediaQuality, MEDIA_CONSTANTS } from "@umituz/react-native-design-system";
9
+ import { useMedia, MediaQuality, MediaValidationError, MEDIA_CONSTANTS } from "@umituz/react-native-design-system";
10
10
  import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
11
11
 
12
12
  export interface PhotoUploadConfig {
@@ -60,7 +60,6 @@ export const usePhotoUploadState = ({
60
60
 
61
61
  const handlePickImage = useCallback(async () => {
62
62
  try {
63
- // Design system handles validation with maxFileSizeMB
64
63
  const result = await pickImage({
65
64
  allowsEditing: true,
66
65
  aspect: [1, 1],
@@ -78,7 +77,7 @@ export const usePhotoUploadState = ({
78
77
  } else if (result.error === MediaValidationError.PERMISSION_DENIED) {
79
78
  Alert.alert(
80
79
  translations.error,
81
- translations.permissionDenied || "Permission to access media library is required",
80
+ translations.permissionDenied ?? "Permission to access media library is required",
82
81
  );
83
82
  }
84
83
  return;
@@ -93,7 +92,6 @@ export const usePhotoUploadState = ({
93
92
  return;
94
93
  }
95
94
 
96
- // Create uploaded image object
97
95
  const uploadedImage: UploadedImage = {
98
96
  uri: selectedAsset.uri,
99
97
  previewUrl: selectedAsset.uri,
@@ -105,7 +103,7 @@ export const usePhotoUploadState = ({
105
103
  setImage(uploadedImage);
106
104
 
107
105
  if (typeof __DEV__ !== "undefined" && __DEV__) {
108
- const fileSizeMB = (selectedAsset.fileSize || 0) / (1024 * 1024);
106
+ const fileSizeMB = (selectedAsset.fileSize ?? 0) / (1024 * 1024);
109
107
  console.log("[usePhotoUploadState] Image selected", {
110
108
  width: uploadedImage.width,
111
109
  height: uploadedImage.height,
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Global type declarations for React Native environment
3
+ */
4
+
5
+ /** React Native development flag */
6
+ declare const __DEV__: boolean;
7
+
8
+ /** Extend NodeJS namespace for React Native compatibility */
9
+ declare namespace NodeJS {
10
+ interface Global {
11
+ __DEV__: boolean;
12
+ }
13
+ }
@@ -22,7 +22,6 @@ export type {
22
22
  UseGenerationOrchestratorReturn,
23
23
  ModerationCallbacks,
24
24
  ModerationResult,
25
- CreditCallbacks,
26
25
  LifecycleConfig,
27
26
  GenerationErrorConfig,
28
27
  GenerationErrorTranslations,
@@ -1,14 +1,14 @@
1
1
  /**
2
2
  * Generation Orchestrator
3
- * Feature-agnostic hook for AI generation with:
4
- * - Network check (via design system's useOfflineStore)
3
+ * Handles AI generation execution with:
4
+ * - Network check
5
5
  * - Content moderation (optional)
6
- * - Credit management (via subscription package)
7
- * - Error handling & alerts
8
6
  * - Progress tracking
9
- * - Lifecycle management
7
+ * - Credit deduction (after success)
8
+ * - Error handling
10
9
  *
11
- * NOTE: Auth is handled by useFeatureGate before generation starts
10
+ * NOTE: Credit CHECK is handled by useFeatureGate before generation starts.
11
+ * This orchestrator only DEDUCTS credits after successful generation.
12
12
  */
13
13
 
14
14
  import { useState, useCallback, useRef, useEffect } from "react";
@@ -33,9 +33,6 @@ const INITIAL_STATE = {
33
33
  error: null,
34
34
  };
35
35
 
36
- const DEFAULT_COMPLETE_DELAY = 500;
37
- const DEFAULT_RESET_DELAY = 1000;
38
-
39
36
  export const useGenerationOrchestrator = <TInput, TResult>(
40
37
  strategy: GenerationStrategy<TInput, TResult>,
41
38
  config: GenerationConfig,
@@ -47,76 +44,35 @@ export const useGenerationOrchestrator = <TInput, TResult>(
47
44
  onSuccess,
48
45
  onError,
49
46
  moderation,
50
- credits,
51
47
  lifecycle,
52
48
  } = config;
53
49
 
54
50
  if (typeof __DEV__ !== "undefined" && __DEV__) {
55
- console.log("[Orchestrator] 🎬 Hook initialized:", {
56
- userId,
57
- hasModeration: !!moderation,
58
- hasCreditsCallbacks: !!credits,
59
- hasLifecycle: !!lifecycle,
60
- });
51
+ console.log("[Orchestrator] Hook initialized:", { userId });
61
52
  }
62
53
 
63
54
  const [state, setState] = useState<GenerationState<TResult>>(INITIAL_STATE);
64
55
  const isGeneratingRef = useRef(false);
65
- const pendingInputRef = useRef<TInput | null>(null);
66
- const completeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
67
- const resetTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
68
56
  const isMountedRef = useRef(true);
69
57
 
70
58
  const offlineStore = useOfflineStore();
71
59
  const { showError, showSuccess } = useAlert();
72
- const creditHook = useDeductCredit({ userId, onCreditsExhausted });
73
-
74
- const defaultCredits = {
75
- checkCredits: creditHook.checkCredits,
76
- deductCredit: async (amount: number): Promise<boolean> => {
77
- return creditHook.deductCredit(amount);
78
- },
79
- };
80
-
81
- const checkCredits = credits?.checkCredits ?? defaultCredits.checkCredits;
82
- const deductCredit = credits?.deductCredits ?? defaultCredits.deductCredit;
83
- const handleCreditsExhausted = credits?.onCreditsExhausted ?? onCreditsExhausted;
60
+ const { deductCredit } = useDeductCredit({ userId, onCreditsExhausted });
84
61
 
85
62
  useEffect(() => {
86
63
  isMountedRef.current = true;
87
64
  return () => {
88
65
  isMountedRef.current = false;
89
- if (completeTimeoutRef.current) clearTimeout(completeTimeoutRef.current);
90
- if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);
91
- if (typeof __DEV__ !== "undefined" && __DEV__) {
92
- console.log("[Orchestrator] 🧹 Cleanup");
93
- }
94
66
  };
95
67
  }, []);
96
68
 
97
69
  const handleLifecycleComplete = useCallback(
98
70
  (status: "success" | "error", result?: TResult, error?: GenerationError) => {
99
71
  if (!lifecycle?.onComplete) return;
100
-
101
- const delay = lifecycle.completeDelay ?? DEFAULT_COMPLETE_DELAY;
102
-
103
- if (completeTimeoutRef.current) clearTimeout(completeTimeoutRef.current);
104
-
105
- completeTimeoutRef.current = setTimeout(() => {
106
- if (!isMountedRef.current) return;
107
-
108
- lifecycle.onComplete?.(status, result, error);
109
-
110
- if (lifecycle.autoReset) {
111
- const resetDelay = lifecycle.resetDelay ?? DEFAULT_RESET_DELAY;
112
- if (resetTimeoutRef.current) clearTimeout(resetTimeoutRef.current);
113
-
114
- resetTimeoutRef.current = setTimeout(() => {
115
- if (isMountedRef.current) {
116
- setState(INITIAL_STATE);
117
- isGeneratingRef.current = false;
118
- }
119
- }, resetDelay);
72
+ const delay = lifecycle.completeDelay ?? 500;
73
+ setTimeout(() => {
74
+ if (isMountedRef.current) {
75
+ lifecycle.onComplete?.(status, result, error);
120
76
  }
121
77
  }, delay);
122
78
  },
@@ -125,11 +81,10 @@ export const useGenerationOrchestrator = <TInput, TResult>(
125
81
 
126
82
  const executeGeneration = useCallback(
127
83
  async (input: TInput) => {
128
- const creditCost = strategy.getCreditCost();
129
-
130
84
  setState((prev) => ({ ...prev, status: "generating", progress: 10 }));
85
+
131
86
  if (typeof __DEV__ !== "undefined" && __DEV__) {
132
- console.log("[Orchestrator] 🎨 Starting generation");
87
+ console.log("[Orchestrator] Starting generation");
133
88
  }
134
89
 
135
90
  const result = await strategy.execute(input, (progress) => {
@@ -145,7 +100,7 @@ export const useGenerationOrchestrator = <TInput, TResult>(
145
100
  } catch (saveErr) {
146
101
  throw createGenerationError(
147
102
  "save",
148
- "Failed to save",
103
+ alertMessages.saveFailed,
149
104
  saveErr instanceof Error ? saveErr : undefined,
150
105
  );
151
106
  }
@@ -153,9 +108,10 @@ export const useGenerationOrchestrator = <TInput, TResult>(
153
108
 
154
109
  if (isMountedRef.current) setState((prev) => ({ ...prev, progress: 90 }));
155
110
 
111
+ const creditCost = strategy.getCreditCost();
156
112
  const creditDeducted = await deductCredit(creditCost);
157
113
  if (!creditDeducted) {
158
- throw createGenerationError("credits", "Failed to deduct credits");
114
+ throw createGenerationError("credits", alertMessages.creditFailed);
159
115
  }
160
116
 
161
117
  if (isMountedRef.current) {
@@ -163,10 +119,12 @@ export const useGenerationOrchestrator = <TInput, TResult>(
163
119
  }
164
120
 
165
121
  if (typeof __DEV__ !== "undefined" && __DEV__) {
166
- console.log("[Orchestrator] 🎉 Generation SUCCESS");
122
+ console.log("[Orchestrator] Generation SUCCESS");
167
123
  }
168
124
 
169
- if (alertMessages.success) void showSuccess("Success", alertMessages.success);
125
+ if (alertMessages.success) {
126
+ showSuccess("Success", alertMessages.success);
127
+ }
170
128
  onSuccess?.(result);
171
129
  handleLifecycleComplete("success", result);
172
130
 
@@ -178,27 +136,17 @@ export const useGenerationOrchestrator = <TInput, TResult>(
178
136
  const generate = useCallback(
179
137
  async (input: TInput) => {
180
138
  if (typeof __DEV__ !== "undefined" && __DEV__) {
181
- console.log("[Orchestrator] 🚀 generate() called");
139
+ console.log("[Orchestrator] generate() called");
182
140
  }
183
141
 
184
142
  if (isGeneratingRef.current) return;
185
143
 
186
144
  isGeneratingRef.current = true;
187
- pendingInputRef.current = input;
188
145
  setState({ ...INITIAL_STATE, status: "checking", isGenerating: true });
189
146
 
190
147
  try {
191
148
  if (!offlineStore.isOnline) {
192
- throw createGenerationError("network", "No internet connection");
193
- }
194
-
195
- const creditCost = strategy.getCreditCost();
196
- const hasCredits = await checkCredits(creditCost);
197
- if (!hasCredits) {
198
- isGeneratingRef.current = false;
199
- setState(INITIAL_STATE);
200
- handleCreditsExhausted?.();
201
- return;
149
+ throw createGenerationError("network", alertMessages.networkError);
202
150
  }
203
151
 
204
152
  if (moderation) {
@@ -221,7 +169,7 @@ export const useGenerationOrchestrator = <TInput, TResult>(
221
169
  if (isMountedRef.current) {
222
170
  setState({ status: "error", isGenerating: false, progress: 0, result: null, error });
223
171
  }
224
- void showError("Error", getAlertMessage(error, alertMessages));
172
+ showError("Error", getAlertMessage(error, alertMessages));
225
173
  onError?.(error);
226
174
  handleLifecycleComplete("error", undefined, error);
227
175
  } finally {
@@ -231,7 +179,7 @@ export const useGenerationOrchestrator = <TInput, TResult>(
231
179
  );
232
180
  return;
233
181
  }
234
- throw createGenerationError("policy", "Content policy violation");
182
+ throw createGenerationError("policy", alertMessages.policyViolation);
235
183
  }
236
184
  }
237
185
 
@@ -239,12 +187,12 @@ export const useGenerationOrchestrator = <TInput, TResult>(
239
187
  } catch (err) {
240
188
  const error = parseError(err);
241
189
  if (typeof __DEV__ !== "undefined" && __DEV__) {
242
- console.log("[Orchestrator] Error:", error);
190
+ console.log("[Orchestrator] Error:", error);
243
191
  }
244
192
  if (isMountedRef.current) {
245
193
  setState({ status: "error", isGenerating: false, progress: 0, result: null, error });
246
194
  }
247
- void showError("Error", getAlertMessage(error, alertMessages));
195
+ showError("Error", getAlertMessage(error, alertMessages));
248
196
  onError?.(error);
249
197
  handleLifecycleComplete("error", undefined, error);
250
198
  throw error;
@@ -254,11 +202,8 @@ export const useGenerationOrchestrator = <TInput, TResult>(
254
202
  },
255
203
  [
256
204
  moderation,
257
- strategy,
258
205
  alertMessages,
259
206
  offlineStore.isOnline,
260
- checkCredits,
261
- handleCreditsExhausted,
262
207
  executeGeneration,
263
208
  showError,
264
209
  onError,
@@ -19,72 +19,73 @@ export interface GenerationStrategy<TInput, TResult> {
19
19
  }
20
20
 
21
21
  export interface AlertMessages {
22
- networkError: string;
23
- policyViolation: string;
24
- saveFailed: string;
25
- creditFailed: string;
26
- unknown: string;
27
- success?: string;
22
+ readonly networkError: string;
23
+ readonly policyViolation: string;
24
+ readonly saveFailed: string;
25
+ readonly creditFailed: string;
26
+ readonly unknown: string;
27
+ readonly success?: string;
28
28
  }
29
29
 
30
30
  export interface ModerationResult {
31
- allowed: boolean;
32
- warnings: string[];
31
+ readonly allowed: boolean;
32
+ readonly warnings: string[];
33
33
  }
34
34
 
35
35
  export interface ModerationCallbacks {
36
- checkContent: (input: unknown) => Promise<ModerationResult>;
37
- onShowWarning?: (warnings: string[], onCancel: () => void, onContinue: () => void) => void;
38
- }
39
-
40
- export interface CreditCallbacks {
41
- checkCredits: (cost: number) => Promise<boolean>;
42
- deductCredits: (cost: number) => Promise<boolean>;
43
- onCreditsExhausted?: () => void;
36
+ readonly checkContent: (input: unknown) => Promise<ModerationResult>;
37
+ readonly onShowWarning?: (
38
+ warnings: string[],
39
+ onCancel: () => void,
40
+ onContinue: () => void
41
+ ) => void;
44
42
  }
45
43
 
46
44
  export interface LifecycleConfig {
47
- onComplete?: (status: "success" | "error", result?: unknown, error?: GenerationError) => void;
48
- completeDelay?: number;
49
- autoReset?: boolean;
50
- resetDelay?: number;
45
+ readonly onComplete?: (
46
+ status: "success" | "error",
47
+ result?: unknown,
48
+ error?: GenerationError
49
+ ) => void;
50
+ readonly completeDelay?: number;
51
+ readonly autoReset?: boolean;
52
+ readonly resetDelay?: number;
51
53
  }
52
54
 
53
55
  export interface GenerationConfig {
54
- userId: string | undefined;
55
- alertMessages: AlertMessages;
56
- onCreditsExhausted?: () => void;
57
- onSuccess?: (result: unknown) => void;
58
- onError?: (error: GenerationError) => void;
59
- moderation?: ModerationCallbacks;
60
- credits?: CreditCallbacks;
61
- lifecycle?: LifecycleConfig;
56
+ readonly userId: string | undefined;
57
+ readonly alertMessages: AlertMessages;
58
+ readonly onCreditsExhausted?: () => void;
59
+ readonly onSuccess?: (result: unknown) => void;
60
+ readonly onError?: (error: GenerationError) => void;
61
+ readonly moderation?: ModerationCallbacks;
62
+ readonly lifecycle?: LifecycleConfig;
62
63
  }
63
64
 
64
65
  export interface GenerationState<TResult> {
65
- status: OrchestratorStatus;
66
- isGenerating: boolean;
67
- progress: number;
68
- result: TResult | null;
69
- error: GenerationError | null;
66
+ readonly status: OrchestratorStatus;
67
+ readonly isGenerating: boolean;
68
+ readonly progress: number;
69
+ readonly result: TResult | null;
70
+ readonly error: GenerationError | null;
70
71
  }
71
72
 
72
73
  export interface GenerationError {
73
- type: GenerationErrorType;
74
- message: string;
75
- originalError?: Error;
74
+ readonly type: GenerationErrorType;
75
+ readonly message: string;
76
+ readonly originalError?: Error;
76
77
  }
77
78
 
78
79
  export type GenerationErrorType = "network" | "credits" | "policy" | "save" | "unknown";
79
80
 
80
81
  export interface UseGenerationOrchestratorReturn<TInput, TResult> {
81
- generate: (input: TInput) => Promise<TResult | void>;
82
- reset: () => void;
83
- status: OrchestratorStatus;
84
- isGenerating: boolean;
85
- progress: number;
86
- result: TResult | null;
87
- error: GenerationError | null;
82
+ readonly generate: (input: TInput) => Promise<TResult | void>;
83
+ readonly reset: () => void;
84
+ readonly status: OrchestratorStatus;
85
+ readonly isGenerating: boolean;
86
+ readonly progress: number;
87
+ readonly result: TResult | null;
88
+ readonly error: GenerationError | null;
88
89
  }
89
90
 
90
91
  export interface GenerationErrorConfig {