@umituz/react-native-ai-generation-content 1.72.6 → 1.72.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umituz/react-native-ai-generation-content",
3
- "version": "1.72.6",
3
+ "version": "1.72.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",
@@ -71,12 +71,9 @@ export function useCreationPersistence(
71
71
 
72
72
  if (creditCost && onCreditDeduct) {
73
73
  onCreditDeduct(creditCost).catch((error) => {
74
- // Log credit deduction errors for debugging
75
74
  if (typeof __DEV__ !== "undefined" && __DEV__) {
76
75
  console.error("[CreationPersistence] Credit deduction failed:", error);
77
76
  }
78
- // Don't throw - credit deduction failure shouldn't block completion
79
- // But we should notify user or retry in production
80
77
  });
81
78
  }
82
79
  },
@@ -89,6 +89,19 @@ export function useProcessingJobsPoller(
89
89
  if (typeof __DEV__ !== "undefined" && __DEV__) console.log("[ProcessingJobsPoller] Completed:", creation.id, urls);
90
90
 
91
91
  const uri = urls.videoUrl || urls.imageUrl || "";
92
+
93
+ // Validate that we have a valid URI before marking as completed
94
+ if (!uri || uri.trim() === "") {
95
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
96
+ console.error("[ProcessingJobsPoller] No valid URI in result:", creation.id);
97
+ }
98
+ await repository.update(userId, creation.id, {
99
+ status: CREATION_STATUS.FAILED,
100
+ metadata: { error: "No valid result URL received" },
101
+ });
102
+ return;
103
+ }
104
+
92
105
  await repository.update(userId, creation.id, {
93
106
  status: CREATION_STATUS.COMPLETED,
94
107
  uri,
@@ -111,6 +124,12 @@ export function useProcessingJobsPoller(
111
124
  }
112
125
  };
113
126
 
127
+ // Use ref to always get latest creations
128
+ const creationsRef = useRef(creations);
129
+ useEffect(() => {
130
+ creationsRef.current = creations;
131
+ }, [creations]);
132
+
114
133
  useEffect(() => {
115
134
  if (!enabled || !userId || processingJobIds.length === 0) {
116
135
  if (intervalRef.current) {
@@ -120,9 +139,9 @@ export function useProcessingJobsPoller(
120
139
  return;
121
140
  }
122
141
 
123
- // Get current jobs at poll time to avoid stale closures
142
+ // Get current jobs at poll time from ref to avoid stale closures
124
143
  const pollCurrentJobs = () => {
125
- const currentJobs = creations.filter(
144
+ const currentJobs = creationsRef.current.filter(
126
145
  (c) => c.status === CREATION_STATUS.PROCESSING && c.requestId && c.model,
127
146
  );
128
147
  currentJobs.forEach((job) => pollJobRef.current?.(job));
@@ -144,7 +163,7 @@ export function useProcessingJobsPoller(
144
163
  intervalRef.current = null;
145
164
  }
146
165
  };
147
- }, [enabled, userId, processingJobIds, creations]);
166
+ }, [enabled, userId, processingJobIds]);
148
167
 
149
168
  return {
150
169
  processingCount: processingJobs.length,
@@ -3,6 +3,8 @@
3
3
  */
4
4
 
5
5
  import { createStore } from "@umituz/react-native-design-system";
6
+
7
+ declare const __DEV__: boolean;
6
8
  import type {
7
9
  FlowState,
8
10
  FlowActions,
@@ -34,7 +36,13 @@ export const createFlowStore = (config: FlowStoreConfig) => {
34
36
  if (config.initialStepIndex !== undefined) {
35
37
  initialIndex = Math.max(0, Math.min(config.initialStepIndex, config.steps.length - 1));
36
38
  } else if (config.initialStepId) {
37
- initialIndex = Math.max(0, config.steps.findIndex((s) => s.id === config.initialStepId));
39
+ const foundIndex = config.steps.findIndex((s) => s.id === config.initialStepId);
40
+ if (foundIndex === -1) {
41
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
42
+ console.warn(`[FlowStore] Step "${config.initialStepId}" not found, using first step`);
43
+ }
44
+ }
45
+ initialIndex = Math.max(0, foundIndex);
38
46
  }
39
47
 
40
48
  const initialStepId = config.steps[initialIndex]?.id ?? "";
@@ -86,28 +94,30 @@ export const createFlowStore = (config: FlowStoreConfig) => {
86
94
  setScenario: (scenario: unknown) => set({ selectedScenario: scenario }),
87
95
 
88
96
  setPartnerImage: (partnerId: string, image: FlowUploadedImageData | undefined) =>
89
- set({ partners: { ...get().partners, [partnerId]: image } }),
97
+ set((state) => ({ partners: { ...state.partners, [partnerId]: image } })),
90
98
 
91
99
  setPartnerName: (partnerId: string, name: string) =>
92
- set({ partnerNames: { ...get().partnerNames, [partnerId]: name } }),
100
+ set((state) => ({ partnerNames: { ...state.partnerNames, [partnerId]: name } })),
93
101
 
94
102
  setTextInput: (text: string) => set({ textInput: text }),
95
103
  setVisualStyle: (styleId: string) => set({ visualStyle: styleId }),
96
104
 
97
105
  setSelectedFeatures: (featureType: string, ids: readonly string[]) =>
98
- set({ selectedFeatures: { ...get().selectedFeatures, [featureType]: ids } }),
106
+ set((state) => ({ selectedFeatures: { ...state.selectedFeatures, [featureType]: ids } })),
99
107
 
100
108
  setCustomData: (key: string, value: unknown) =>
101
- set({ customData: { ...get().customData, [key]: value } }),
109
+ set((state) => ({ customData: { ...state.customData, [key]: value } })),
102
110
 
103
111
  startGeneration: () =>
104
112
  set({ generationStatus: "preparing", generationProgress: 0, generationError: undefined }),
105
113
 
106
- updateProgress: (progress: number) =>
114
+ updateProgress: (progress: number) => {
115
+ const validProgress = Math.max(0, Math.min(100, progress));
107
116
  set({
108
- generationProgress: progress,
109
- generationStatus: progress >= 100 ? "completed" : progress > 0 ? "generating" : "preparing",
110
- }),
117
+ generationProgress: validProgress,
118
+ generationStatus: validProgress >= 100 ? "completed" : validProgress > 0 ? "generating" : "preparing",
119
+ });
120
+ },
111
121
 
112
122
  setResult: (result: unknown) =>
113
123
  set({ generationResult: result, generationStatus: "completed", generationProgress: 100 }),
@@ -136,6 +136,10 @@ export function validateBase64(input: unknown): ValidationResult {
136
136
  return { isValid: false, errors: ["Input must be a string"] };
137
137
  }
138
138
 
139
+ if (input.length === 0) {
140
+ return { isValid: false, errors: ["Base64 string cannot be empty"] };
141
+ }
142
+
139
143
  const base64Regex = /^[A-Za-z0-9+/]*={0,2}$/;
140
144
  if (!base64Regex.test(input)) {
141
145
  return { isValid: false, errors: ["Invalid base64 format"] };
@@ -48,6 +48,12 @@ export function updatePhotoStep(
48
48
  stepIndex: number,
49
49
  updates: Partial<PhotoStepData>,
50
50
  ): GenerationFlowState {
51
+ // Validate bounds to prevent sparse arrays and undefined access
52
+ if (stepIndex < 0 || stepIndex >= state.photoSteps.length) {
53
+ console.warn(`[FlowState] Invalid stepIndex ${stepIndex}, ignoring update`);
54
+ return state;
55
+ }
56
+
51
57
  const newPhotoSteps = [...state.photoSteps];
52
58
  newPhotoSteps[stepIndex] = {
53
59
  ...newPhotoSteps[stepIndex],
@@ -109,6 +109,9 @@ export function useGeneration<T = unknown>(
109
109
  if (!abortRef.current && !abortControllerRef.current?.signal.aborted) {
110
110
  setIsGenerating(false);
111
111
  }
112
+ if (abortControllerRef.current && !abortControllerRef.current.signal.aborted) {
113
+ abortControllerRef.current.abort();
114
+ }
112
115
  abortControllerRef.current = null;
113
116
  }
114
117
  },