@umituz/react-native-ai-generation-content 1.72.27 → 1.72.29

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.27",
3
+ "version": "1.72.29",
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",
@@ -42,7 +42,7 @@ export const TEXT_TO_VIDEO_WIZARD_CONFIG: WizardFeatureConfig = {
42
42
  { id: "9:16", label: "9:16", value: "9:16" },
43
43
  ],
44
44
  required: true,
45
- defaultValue: "16:9",
45
+ defaultValue: "9:16",
46
46
  },
47
47
  {
48
48
  id: "duration",
@@ -89,13 +89,13 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
89
89
  }
90
90
 
91
91
  case StepType.PARTNER_UPLOAD:
92
- return renderPhotoUploadStep({ step, customData, onBack, onPhotoContinue, t });
92
+ return renderPhotoUploadStep({ key: step.id, step, customData, onBack, onPhotoContinue, t });
93
93
 
94
94
  case StepType.TEXT_INPUT:
95
- return renderTextInputStep({ step, customData, onBack, onPhotoContinue, t, alertMessages });
95
+ return renderTextInputStep({ key: step.id, step, customData, onBack, onPhotoContinue, t, alertMessages });
96
96
 
97
97
  case StepType.FEATURE_SELECTION:
98
- return renderSelectionStep({ step, customData, onBack, onPhotoContinue, t });
98
+ return renderSelectionStep({ key: step.id, step, customData, onBack, onPhotoContinue, t });
99
99
 
100
100
  default:
101
101
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -46,3 +46,15 @@ export function getSelectionConfig(config: unknown): SelectionStepConfig | undef
46
46
  if (config.type === "selection") return config as unknown as SelectionStepConfig;
47
47
  return undefined;
48
48
  }
49
+
50
+ export function getSelectionValue(data: unknown): string | string[] | undefined {
51
+ if (typeof data === "string" || Array.isArray(data)) return data as string | string[];
52
+ if (isRecord(data) && "selection" in data) return data.selection as string | string[];
53
+ return undefined;
54
+ }
55
+
56
+ export function getTextInputValue(data: unknown): string | undefined {
57
+ if (typeof data === "string") return data;
58
+ if (isRecord(data) && "text" in data) return String(data.text);
59
+ return undefined;
60
+ }
@@ -9,6 +9,7 @@ import type { StepDefinition } from "../../../../../../domain/entities/flow-conf
9
9
  import type { UploadedImage } from "../../../../../../presentation/hooks/generation/useAIGenerateState";
10
10
 
11
11
  export interface PhotoUploadStepProps {
12
+ readonly key?: string;
12
13
  readonly step: StepDefinition;
13
14
  readonly customData: Record<string, unknown>;
14
15
  readonly onBack: () => void;
@@ -30,6 +31,7 @@ export function renderPhotoUploadStep({
30
31
 
31
32
  return (
32
33
  <GenericPhotoUploadScreen
34
+ key={step.id}
33
35
  stepId={step.id}
34
36
  translations={{
35
37
  title: t(titleKey),
@@ -4,11 +4,12 @@
4
4
 
5
5
  import React from "react";
6
6
  import { SelectionScreen } from "../../screens/SelectionScreen";
7
- import { getSelectionConfig } from "../WizardStepRenderer.utils";
7
+ import { getSelectionConfig, getSelectionValue } from "../WizardStepRenderer.utils";
8
8
  import type { StepDefinition } from "../../../../../../domain/entities/flow-config.types";
9
9
  import type { UploadedImage } from "../../../../../../presentation/hooks/generation/useAIGenerateState";
10
10
 
11
11
  export interface SelectionStepProps {
12
+ readonly key?: string;
12
13
  readonly step: StepDefinition;
13
14
  readonly customData: Record<string, unknown>;
14
15
  readonly onBack: () => void;
@@ -32,7 +33,7 @@ export function renderSelectionStep({
32
33
  const isRequired = step.required ?? true;
33
34
 
34
35
  // Priority: existing value > config default > auto-select single option
35
- const existingValue = customData[step.id] as string | string[] | undefined;
36
+ const existingValue = getSelectionValue(customData[step.id]);
36
37
  const configDefault = selectionConfig?.defaultValue;
37
38
  const autoSelectValue = isRequired && options.length === 1 ? options[0].id : undefined;
38
39
  const initialValue = existingValue ?? configDefault ?? autoSelectValue;
@@ -55,6 +56,7 @@ export function renderSelectionStep({
55
56
 
56
57
  return (
57
58
  <SelectionScreen
59
+ key={step.id}
58
60
  stepId={step.id}
59
61
  translations={{
60
62
  title: t(titleKey),
@@ -4,12 +4,13 @@
4
4
 
5
5
  import React from "react";
6
6
  import { TextInputScreen } from "../../screens/TextInputScreen";
7
- import { getTextInputConfig } from "../WizardStepRenderer.utils";
7
+ import { getTextInputConfig, getTextInputValue } from "../WizardStepRenderer.utils";
8
8
  import type { StepDefinition } from "../../../../../../domain/entities/flow-config.types";
9
9
  import type { UploadedImage } from "../../../../../../presentation/hooks/generation/useAIGenerateState";
10
10
  import type { AlertMessages } from "../../../../../../presentation/hooks/generation/types";
11
11
 
12
12
  export interface TextInputStepProps {
13
+ readonly key?: string;
13
14
  readonly step: StepDefinition;
14
15
  readonly customData: Record<string, unknown>;
15
16
  readonly onBack: () => void;
@@ -30,16 +31,11 @@ export function renderTextInputStep({
30
31
  const titleKey = textConfig?.titleKey ?? `wizard.steps.${step.id}.title`;
31
32
  const subtitleKey = textConfig?.subtitleKey ?? `wizard.steps.${step.id}.subtitle`;
32
33
  const placeholderKey = textConfig?.placeholderKey ?? `wizard.steps.${step.id}.placeholder`;
33
- const existingData = customData[step.id];
34
- const existingText =
35
- typeof existingData === "string"
36
- ? existingData
37
- : typeof existingData === "object" && existingData !== null && "text" in existingData
38
- ? String((existingData as { text: string }).text)
39
- : "";
34
+ const existingText = getTextInputValue(customData[step.id]) ?? "";
40
35
 
41
36
  return (
42
37
  <TextInputScreen
38
+ key={step.id}
43
39
  stepId={step.id}
44
40
  translations={{
45
41
  title: t(titleKey),
@@ -36,17 +36,18 @@ export const SelectionScreen: React.FC<SelectionScreenProps> = ({
36
36
  }) => {
37
37
  const tokens = useAppDesignTokens();
38
38
  const [selected, setSelected] = useState<string | string[]>(() => {
39
- const initialState = initialValue ? initialValue : (config?.multiSelect ? [] : "");
40
- if (typeof __DEV__ !== "undefined" && __DEV__) {
41
- console.log("[SelectionScreen] Initial state:", {
42
- stepId,
43
- initialValue,
44
- initialState,
45
- hasInitialValue: !!initialValue,
46
- multiSelect: config?.multiSelect,
47
- });
39
+ const isMulti = config?.multiSelect ?? false;
40
+ const optionIds = options.map((opt) => opt.id);
41
+
42
+ if (isMulti) {
43
+ const initialArr = initialValue && Array.isArray(initialValue) ? initialValue : [];
44
+ // Filter out invalid IDs
45
+ return initialArr.filter((id) => optionIds.includes(id));
48
46
  }
49
- return initialState;
47
+
48
+ const initialStr = typeof initialValue === "string" ? initialValue : "";
49
+ // Ensure the initial string is a valid option ID
50
+ return optionIds.includes(initialStr) ? initialStr : "";
50
51
  });
51
52
 
52
53
  const isMultiSelect = config?.multiSelect ?? false;
@@ -112,7 +113,11 @@ export const SelectionScreen: React.FC<SelectionScreenProps> = ({
112
113
  <View style={[styles.checkmark, { backgroundColor: tokens.colors.primary }]}>
113
114
  <AtomicIcon name="checkmark" size="xs" color="onPrimary" />
114
115
  </View>
115
- ) : null}
116
+ ) : (
117
+ !isMultiSelect && (
118
+ <View style={[styles.radioOutline, { borderColor: tokens.colors.border }]} />
119
+ )
120
+ )}
116
121
  </TouchableOpacity>
117
122
  );
118
123
  };
@@ -163,4 +168,13 @@ const createStyles = (tokens: DesignTokens) =>
163
168
  alignItems: "center",
164
169
  justifyContent: "center",
165
170
  },
171
+ radioOutline: {
172
+ position: "absolute",
173
+ top: tokens.spacing.xs,
174
+ right: tokens.spacing.xs,
175
+ width: 20,
176
+ height: 20,
177
+ borderRadius: 10,
178
+ borderWidth: 1,
179
+ },
166
180
  });
@@ -5,7 +5,6 @@
5
5
 
6
6
  import { useState, useCallback, useRef, useEffect } from "react";
7
7
  import { useOfflineStore, useAlert } from "@umituz/react-native-design-system";
8
- import { useDeductCredit } from "@umituz/react-native-subscription";
9
8
  import { createGenerationError, getAlertMessage, parseError } from "./errors";
10
9
  import { handleModeration } from "./moderation-handler";
11
10
  import type {
@@ -38,7 +37,6 @@ export const useGenerationOrchestrator = <TInput, TResult>(
38
37
 
39
38
  const offlineStore = useOfflineStore();
40
39
  const { showError, showSuccess } = useAlert();
41
- const { deductCredit, checkCredits } = useDeductCredit({ userId, onCreditsExhausted });
42
40
 
43
41
  useEffect(() => {
44
42
  isMountedRef.current = true;
@@ -81,23 +79,6 @@ export const useGenerationOrchestrator = <TInput, TResult>(
81
79
  console.log("[Orchestrator] executeGeneration() called");
82
80
  }
83
81
 
84
- const creditCost = strategy.getCreditCost();
85
- if (typeof __DEV__ !== "undefined" && __DEV__) {
86
- console.log("[Orchestrator] Deducting credits:", creditCost);
87
- }
88
-
89
- const creditDeducted = await deductCredit(creditCost);
90
- if (!creditDeducted) {
91
- if (typeof __DEV__ !== "undefined" && __DEV__) {
92
- console.log("[Orchestrator] ERROR: Credit deduction failed");
93
- }
94
- throw createGenerationError("credits", alertMessages.creditFailed);
95
- }
96
-
97
- if (typeof __DEV__ !== "undefined" && __DEV__) {
98
- console.log("[Orchestrator] Credits deducted successfully");
99
- }
100
-
101
82
  setState((prev) => ({ ...prev, status: "generating" }));
102
83
  if (typeof __DEV__ !== "undefined" && __DEV__) {
103
84
  console.log("[Orchestrator] State: generating - calling strategy.execute()");
@@ -137,7 +118,7 @@ export const useGenerationOrchestrator = <TInput, TResult>(
137
118
  handleLifecycleComplete("success", result);
138
119
  return result;
139
120
  },
140
- [strategy, userId, alertMessages, deductCredit, showSuccess, onSuccess, handleLifecycleComplete],
121
+ [strategy, userId, alertMessages, showSuccess, onSuccess, handleLifecycleComplete],
141
122
  );
142
123
 
143
124
  const generate = useCallback(
@@ -178,33 +159,6 @@ export const useGenerationOrchestrator = <TInput, TResult>(
178
159
  console.log("[Orchestrator] Online check passed");
179
160
  }
180
161
 
181
- // Check if aborted
182
- if (abortControllerRef.current.signal.aborted) {
183
- if (typeof __DEV__ !== "undefined" && __DEV__) {
184
- console.log("[Orchestrator] ERROR: Generation aborted (1)");
185
- }
186
- throw new Error("Generation aborted");
187
- }
188
-
189
- // Pre-validate credits before generation to catch concurrent consumption
190
- const creditCost = strategy.getCreditCost();
191
- if (typeof __DEV__ !== "undefined" && __DEV__) {
192
- console.log("[Orchestrator] Checking credits - cost:", creditCost);
193
- }
194
-
195
- const hasEnoughCredits = await checkCredits(creditCost);
196
- if (!hasEnoughCredits) {
197
- if (typeof __DEV__ !== "undefined" && __DEV__) {
198
- console.log("[Orchestrator] ERROR: Pre-validation failed - insufficient credits");
199
- }
200
- onCreditsExhausted?.();
201
- throw createGenerationError("credits", alertMessages.creditFailed);
202
- }
203
-
204
- if (typeof __DEV__ !== "undefined" && __DEV__) {
205
- console.log("[Orchestrator] Credit check passed");
206
- }
207
-
208
162
  // Check if aborted before moderation
209
163
  if (abortControllerRef.current.signal.aborted) {
210
164
  if (typeof __DEV__ !== "undefined" && __DEV__) {
@@ -251,7 +205,7 @@ export const useGenerationOrchestrator = <TInput, TResult>(
251
205
  abortControllerRef.current = null;
252
206
  }
253
207
  },
254
- [offlineStore, moderation, alertMessages, strategy, checkCredits, onCreditsExhausted, executeGeneration, showError, onError, handleLifecycleComplete],
208
+ [offlineStore, moderation, alertMessages, strategy, executeGeneration, showError, onError, handleLifecycleComplete],
255
209
  );
256
210
 
257
211
  const reset = useCallback(() => {
@@ -14,7 +14,6 @@ export type OrchestratorStatus =
14
14
 
15
15
  export interface GenerationStrategy<TInput, TResult> {
16
16
  execute: (input: TInput) => Promise<TResult>;
17
- getCreditCost: () => number;
18
17
  save?: (result: TResult, userId: string) => Promise<void>;
19
18
  }
20
19
 
@@ -55,7 +54,6 @@ export interface LifecycleConfig {
55
54
  export interface GenerationConfig {
56
55
  readonly userId: string | undefined;
57
56
  readonly alertMessages: AlertMessages;
58
- readonly onCreditsExhausted?: () => void;
59
57
  readonly onSuccess?: (result: unknown) => void;
60
58
  readonly onError?: (error: GenerationError) => void;
61
59
  readonly moderation?: ModerationCallbacks;