@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 +1 -1
- package/src/domains/generation/wizard/configs/text-to-video.config.ts +1 -1
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.tsx +3 -3
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.utils.ts +12 -0
- package/src/domains/generation/wizard/presentation/components/step-renderers/renderPhotoUploadStep.tsx +2 -0
- package/src/domains/generation/wizard/presentation/components/step-renderers/renderSelectionStep.tsx +4 -2
- package/src/domains/generation/wizard/presentation/components/step-renderers/renderTextInputStep.tsx +4 -8
- package/src/domains/generation/wizard/presentation/screens/SelectionScreen.tsx +25 -11
- package/src/presentation/hooks/generation/orchestrator.ts +2 -48
- package/src/presentation/hooks/generation/types.ts +0 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.72.
|
|
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",
|
|
@@ -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),
|
package/src/domains/generation/wizard/presentation/components/step-renderers/renderSelectionStep.tsx
CHANGED
|
@@ -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]
|
|
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),
|
package/src/domains/generation/wizard/presentation/components/step-renderers/renderTextInputStep.tsx
CHANGED
|
@@ -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
|
|
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
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
-
) :
|
|
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,
|
|
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,
|
|
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;
|