@umituz/react-native-ai-generation-content 1.27.20 → 1.27.22
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-image.config.ts +14 -19
- package/src/domains/generation/wizard/index.ts +6 -1
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.tsx +40 -1
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.utils.ts +17 -1
- package/src/domains/generation/wizard/presentation/screens/TextInputScreen.tsx +219 -0
- package/src/domains/generation/wizard/presentation/screens/index.ts +6 -0
- package/src/features/text-to-image/presentation/hooks/useGeneration.ts +3 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.27.
|
|
3
|
+
"version": "1.27.22",
|
|
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",
|
|
@@ -3,27 +3,22 @@
|
|
|
3
3
|
* Config-driven wizard steps for text-to-image generation
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import type { WizardFeatureConfig } from "../domain/entities/wizard-config.types";
|
|
6
|
+
import type { WizardFeatureConfig, TextInputStepConfig } from "../domain/entities/wizard-config.types";
|
|
7
|
+
|
|
8
|
+
const promptStep: TextInputStepConfig = {
|
|
9
|
+
id: "prompt",
|
|
10
|
+
type: "text_input",
|
|
11
|
+
required: true,
|
|
12
|
+
titleKey: "text2image.wizard.prompt.title",
|
|
13
|
+
subtitleKey: "text2image.wizard.prompt.subtitle",
|
|
14
|
+
placeholderKey: "text2image.wizard.prompt.placeholder",
|
|
15
|
+
minLength: 3,
|
|
16
|
+
maxLength: 1000,
|
|
17
|
+
multiline: true,
|
|
18
|
+
};
|
|
7
19
|
|
|
8
20
|
export const TEXT_TO_IMAGE_WIZARD_CONFIG: WizardFeatureConfig = {
|
|
9
21
|
id: "text-to-image",
|
|
10
22
|
name: "Text to Image",
|
|
11
|
-
steps: [
|
|
12
|
-
{
|
|
13
|
-
id: "prompt",
|
|
14
|
-
type: "text_input",
|
|
15
|
-
required: true,
|
|
16
|
-
placeholderKey: "textToImage.promptPlaceholder",
|
|
17
|
-
minLength: 3,
|
|
18
|
-
maxLength: 1000,
|
|
19
|
-
multiline: true,
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
id: "style",
|
|
23
|
-
type: "selection",
|
|
24
|
-
selectionType: "style",
|
|
25
|
-
options: [],
|
|
26
|
-
required: false,
|
|
27
|
-
},
|
|
28
|
-
],
|
|
23
|
+
steps: [promptStep],
|
|
29
24
|
};
|
|
@@ -50,7 +50,12 @@ export { GenericWizardFlow } from "./presentation/components";
|
|
|
50
50
|
export type { GenericWizardFlowProps } from "./presentation/components";
|
|
51
51
|
|
|
52
52
|
// Presentation - Screens
|
|
53
|
-
export { GeneratingScreen } from "./presentation/screens";
|
|
53
|
+
export { GeneratingScreen, TextInputScreen } from "./presentation/screens";
|
|
54
|
+
export type {
|
|
55
|
+
TextInputScreenTranslations,
|
|
56
|
+
TextInputScreenConfig,
|
|
57
|
+
TextInputScreenProps,
|
|
58
|
+
} from "./presentation/screens";
|
|
54
59
|
|
|
55
60
|
// Feature Configs
|
|
56
61
|
export * from "./configs";
|
|
@@ -3,9 +3,10 @@ import { extractMediaUrl, getMediaTypeFromUrl } from "@umituz/react-native-desig
|
|
|
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
|
+
import { TextInputScreen } from "../screens/TextInputScreen";
|
|
6
7
|
import { ScenarioPreviewScreen } from "../../../../scenarios/presentation/screens/ScenarioPreviewScreen";
|
|
7
8
|
import { ResultPreviewScreen } from "../../../../result-preview/presentation/components/ResultPreviewScreen";
|
|
8
|
-
import { getWizardStepConfig, getUploadedImage } from "./WizardStepRenderer.utils";
|
|
9
|
+
import { getWizardStepConfig, getTextInputConfig, getUploadedImage } from "./WizardStepRenderer.utils";
|
|
9
10
|
import type { WizardStepRendererProps } from "./WizardStepRenderer.types";
|
|
10
11
|
|
|
11
12
|
export type { WizardStepRendererProps } from "./WizardStepRenderer.types";
|
|
@@ -127,6 +128,44 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
|
|
|
127
128
|
);
|
|
128
129
|
}
|
|
129
130
|
|
|
131
|
+
case StepType.TEXT_INPUT: {
|
|
132
|
+
const textConfig = getTextInputConfig(step.config);
|
|
133
|
+
const titleKey = textConfig?.titleKey ?? `wizard.steps.${step.id}.title`;
|
|
134
|
+
const subtitleKey = textConfig?.subtitleKey ?? `wizard.steps.${step.id}.subtitle`;
|
|
135
|
+
const placeholderKey = textConfig?.placeholderKey ?? `wizard.steps.${step.id}.placeholder`;
|
|
136
|
+
const existingData = customData[step.id];
|
|
137
|
+
const existingText = typeof existingData === "string"
|
|
138
|
+
? existingData
|
|
139
|
+
: typeof existingData === "object" && existingData !== null && "text" in existingData
|
|
140
|
+
? String((existingData as { text: string }).text)
|
|
141
|
+
: "";
|
|
142
|
+
|
|
143
|
+
return (
|
|
144
|
+
<TextInputScreen
|
|
145
|
+
stepId={step.id}
|
|
146
|
+
translations={{
|
|
147
|
+
title: t(titleKey),
|
|
148
|
+
subtitle: subtitleKey ? t(subtitleKey) : undefined,
|
|
149
|
+
placeholder: t(placeholderKey),
|
|
150
|
+
continueButton: t("common.continue"),
|
|
151
|
+
backButton: t("common.back"),
|
|
152
|
+
examplesTitle: t("textInput.examplesTitle"),
|
|
153
|
+
}}
|
|
154
|
+
config={{
|
|
155
|
+
minLength: textConfig?.minLength ?? 3,
|
|
156
|
+
maxLength: textConfig?.maxLength ?? 1000,
|
|
157
|
+
multiline: textConfig?.multiline ?? true,
|
|
158
|
+
}}
|
|
159
|
+
initialValue={existingText}
|
|
160
|
+
onBack={onBack}
|
|
161
|
+
onContinue={(text) => {
|
|
162
|
+
// Store text in a structure compatible with existing handlers
|
|
163
|
+
onPhotoContinue(step.id, { uri: text, text, previewUrl: "" } as any);
|
|
164
|
+
}}
|
|
165
|
+
/>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
130
169
|
default:
|
|
131
170
|
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
132
171
|
console.warn("[WizardStepRenderer] Unhandled step type", { stepType: step.type });
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
WizardStepConfig,
|
|
3
|
+
TextInputStepConfig,
|
|
4
|
+
PhotoUploadStepConfig,
|
|
5
|
+
} from "../../domain/entities/wizard-config.types";
|
|
2
6
|
import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
|
|
3
7
|
|
|
4
8
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
@@ -19,6 +23,18 @@ export function getWizardStepConfig(config: unknown): WizardStepConfig | undefin
|
|
|
19
23
|
return undefined;
|
|
20
24
|
}
|
|
21
25
|
|
|
26
|
+
export function getTextInputConfig(config: unknown): TextInputStepConfig | undefined {
|
|
27
|
+
if (!isRecord(config)) return undefined;
|
|
28
|
+
if (config.type === "text_input") return config as unknown as TextInputStepConfig;
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function getPhotoUploadConfig(config: unknown): PhotoUploadStepConfig | undefined {
|
|
33
|
+
if (!isRecord(config)) return undefined;
|
|
34
|
+
if (config.type === "photo_upload") return config as unknown as PhotoUploadStepConfig;
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
22
38
|
export function getUploadedImage(data: unknown): UploadedImage | undefined {
|
|
23
39
|
if (isUploadedImage(data)) return data;
|
|
24
40
|
return undefined;
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Input Screen
|
|
3
|
+
* Generic text input step for wizard flows
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import React, { useState, useCallback } from "react";
|
|
7
|
+
import { View, ScrollView, TextInput, StyleSheet } from "react-native";
|
|
8
|
+
import {
|
|
9
|
+
AtomicText,
|
|
10
|
+
AtomicButton,
|
|
11
|
+
AtomicIcon,
|
|
12
|
+
useAppDesignTokens,
|
|
13
|
+
} from "@umituz/react-native-design-system";
|
|
14
|
+
|
|
15
|
+
export interface TextInputScreenTranslations {
|
|
16
|
+
readonly title: string;
|
|
17
|
+
readonly subtitle?: string;
|
|
18
|
+
readonly placeholder: string;
|
|
19
|
+
readonly continueButton: string;
|
|
20
|
+
readonly backButton?: string;
|
|
21
|
+
readonly examplesTitle?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TextInputScreenConfig {
|
|
25
|
+
readonly minLength?: number;
|
|
26
|
+
readonly maxLength?: number;
|
|
27
|
+
readonly multiline?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TextInputScreenProps {
|
|
31
|
+
readonly stepId: string;
|
|
32
|
+
readonly translations: TextInputScreenTranslations;
|
|
33
|
+
readonly config?: TextInputScreenConfig;
|
|
34
|
+
readonly examplePrompts?: string[];
|
|
35
|
+
readonly initialValue?: string;
|
|
36
|
+
readonly onBack: () => void;
|
|
37
|
+
readonly onContinue: (text: string) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const TextInputScreen: React.FC<TextInputScreenProps> = ({
|
|
41
|
+
stepId: _stepId,
|
|
42
|
+
translations,
|
|
43
|
+
config,
|
|
44
|
+
examplePrompts = [],
|
|
45
|
+
initialValue = "",
|
|
46
|
+
onBack,
|
|
47
|
+
onContinue,
|
|
48
|
+
}) => {
|
|
49
|
+
const tokens = useAppDesignTokens();
|
|
50
|
+
const [text, setText] = useState(initialValue);
|
|
51
|
+
|
|
52
|
+
const minLength = config?.minLength ?? 3;
|
|
53
|
+
const maxLength = config?.maxLength ?? 1000;
|
|
54
|
+
const canContinue = text.trim().length >= minLength;
|
|
55
|
+
|
|
56
|
+
const handleContinue = useCallback(() => {
|
|
57
|
+
if (canContinue) {
|
|
58
|
+
onContinue(text.trim());
|
|
59
|
+
}
|
|
60
|
+
}, [canContinue, text, onContinue]);
|
|
61
|
+
|
|
62
|
+
const handleExampleSelect = useCallback((example: string) => {
|
|
63
|
+
setText(example);
|
|
64
|
+
}, []);
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
68
|
+
<View style={[styles.header, { paddingHorizontal: tokens.spacing.md }]}>
|
|
69
|
+
<AtomicButton
|
|
70
|
+
variant="text"
|
|
71
|
+
size="sm"
|
|
72
|
+
onPress={onBack}
|
|
73
|
+
>
|
|
74
|
+
<View style={styles.backButtonContent}>
|
|
75
|
+
<AtomicIcon name="arrow-back" size="sm" color="textPrimary" />
|
|
76
|
+
{translations.backButton ? (
|
|
77
|
+
<AtomicText type="labelMedium" color="textPrimary" style={styles.backButtonText}>
|
|
78
|
+
{translations.backButton}
|
|
79
|
+
</AtomicText>
|
|
80
|
+
) : null}
|
|
81
|
+
</View>
|
|
82
|
+
</AtomicButton>
|
|
83
|
+
</View>
|
|
84
|
+
|
|
85
|
+
<ScrollView
|
|
86
|
+
style={styles.scrollView}
|
|
87
|
+
contentContainerStyle={{ padding: tokens.spacing.md }}
|
|
88
|
+
keyboardShouldPersistTaps="handled"
|
|
89
|
+
>
|
|
90
|
+
<AtomicText type="headlineMedium" color="textPrimary" style={styles.title}>
|
|
91
|
+
{translations.title}
|
|
92
|
+
</AtomicText>
|
|
93
|
+
|
|
94
|
+
{translations.subtitle ? (
|
|
95
|
+
<AtomicText
|
|
96
|
+
type="bodyMedium"
|
|
97
|
+
color="textSecondary"
|
|
98
|
+
style={{ marginBottom: tokens.spacing.lg }}
|
|
99
|
+
>
|
|
100
|
+
{translations.subtitle}
|
|
101
|
+
</AtomicText>
|
|
102
|
+
) : null}
|
|
103
|
+
|
|
104
|
+
<View
|
|
105
|
+
style={[
|
|
106
|
+
styles.inputContainer,
|
|
107
|
+
{
|
|
108
|
+
backgroundColor: tokens.colors.backgroundSecondary,
|
|
109
|
+
borderRadius: tokens.borders.radius.md,
|
|
110
|
+
borderColor: tokens.colors.border,
|
|
111
|
+
},
|
|
112
|
+
]}
|
|
113
|
+
>
|
|
114
|
+
<TextInput
|
|
115
|
+
style={[
|
|
116
|
+
styles.textInput,
|
|
117
|
+
{
|
|
118
|
+
color: tokens.colors.textPrimary,
|
|
119
|
+
minHeight: config?.multiline ? 120 : 48,
|
|
120
|
+
},
|
|
121
|
+
]}
|
|
122
|
+
placeholder={translations.placeholder}
|
|
123
|
+
placeholderTextColor={tokens.colors.textTertiary}
|
|
124
|
+
value={text}
|
|
125
|
+
onChangeText={setText}
|
|
126
|
+
multiline={config?.multiline ?? true}
|
|
127
|
+
maxLength={maxLength}
|
|
128
|
+
textAlignVertical="top"
|
|
129
|
+
/>
|
|
130
|
+
<AtomicText
|
|
131
|
+
type="bodySmall"
|
|
132
|
+
color="textTertiary"
|
|
133
|
+
style={styles.charCount}
|
|
134
|
+
>
|
|
135
|
+
{text.length}/{maxLength}
|
|
136
|
+
</AtomicText>
|
|
137
|
+
</View>
|
|
138
|
+
|
|
139
|
+
{examplePrompts.length > 0 && translations.examplesTitle ? (
|
|
140
|
+
<View style={{ marginTop: tokens.spacing.lg }}>
|
|
141
|
+
<AtomicText
|
|
142
|
+
type="labelLarge"
|
|
143
|
+
color="textSecondary"
|
|
144
|
+
style={{ marginBottom: tokens.spacing.sm }}
|
|
145
|
+
>
|
|
146
|
+
{translations.examplesTitle}
|
|
147
|
+
</AtomicText>
|
|
148
|
+
{examplePrompts.slice(0, 4).map((example, index) => (
|
|
149
|
+
<AtomicButton
|
|
150
|
+
key={index}
|
|
151
|
+
variant="outline"
|
|
152
|
+
size="sm"
|
|
153
|
+
onPress={() => handleExampleSelect(example)}
|
|
154
|
+
style={{ marginBottom: tokens.spacing.xs }}
|
|
155
|
+
>
|
|
156
|
+
{example.length > 50 ? `${example.slice(0, 50)}...` : example}
|
|
157
|
+
</AtomicButton>
|
|
158
|
+
))}
|
|
159
|
+
</View>
|
|
160
|
+
) : null}
|
|
161
|
+
</ScrollView>
|
|
162
|
+
|
|
163
|
+
<View style={[styles.footer, { padding: tokens.spacing.md }]}>
|
|
164
|
+
<AtomicButton
|
|
165
|
+
variant="primary"
|
|
166
|
+
size="lg"
|
|
167
|
+
onPress={handleContinue}
|
|
168
|
+
disabled={!canContinue}
|
|
169
|
+
style={styles.continueButton}
|
|
170
|
+
>
|
|
171
|
+
{translations.continueButton}
|
|
172
|
+
</AtomicButton>
|
|
173
|
+
</View>
|
|
174
|
+
</View>
|
|
175
|
+
);
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const styles = StyleSheet.create({
|
|
179
|
+
container: {
|
|
180
|
+
flex: 1,
|
|
181
|
+
},
|
|
182
|
+
header: {
|
|
183
|
+
flexDirection: "row",
|
|
184
|
+
alignItems: "center",
|
|
185
|
+
paddingVertical: 8,
|
|
186
|
+
},
|
|
187
|
+
backButtonContent: {
|
|
188
|
+
flexDirection: "row",
|
|
189
|
+
alignItems: "center",
|
|
190
|
+
},
|
|
191
|
+
backButtonText: {
|
|
192
|
+
marginLeft: 4,
|
|
193
|
+
},
|
|
194
|
+
scrollView: {
|
|
195
|
+
flex: 1,
|
|
196
|
+
},
|
|
197
|
+
title: {
|
|
198
|
+
marginBottom: 8,
|
|
199
|
+
},
|
|
200
|
+
inputContainer: {
|
|
201
|
+
borderWidth: 1,
|
|
202
|
+
padding: 12,
|
|
203
|
+
},
|
|
204
|
+
textInput: {
|
|
205
|
+
fontSize: 16,
|
|
206
|
+
lineHeight: 24,
|
|
207
|
+
},
|
|
208
|
+
charCount: {
|
|
209
|
+
textAlign: "right",
|
|
210
|
+
marginTop: 4,
|
|
211
|
+
},
|
|
212
|
+
footer: {
|
|
213
|
+
borderTopWidth: 1,
|
|
214
|
+
borderTopColor: "rgba(0,0,0,0.1)",
|
|
215
|
+
},
|
|
216
|
+
continueButton: {
|
|
217
|
+
width: "100%",
|
|
218
|
+
},
|
|
219
|
+
});
|
|
@@ -5,3 +5,9 @@ export type {
|
|
|
5
5
|
PhotoUploadScreenConfig,
|
|
6
6
|
PhotoUploadScreenProps,
|
|
7
7
|
} from "./GenericPhotoUploadScreen";
|
|
8
|
+
export { TextInputScreen } from "./TextInputScreen";
|
|
9
|
+
export type {
|
|
10
|
+
TextInputScreenTranslations,
|
|
11
|
+
TextInputScreenConfig,
|
|
12
|
+
TextInputScreenProps,
|
|
13
|
+
} from "./TextInputScreen";
|
|
@@ -103,26 +103,8 @@ export function useGeneration(options: UseGenerationOptions): UseGenerationRetur
|
|
|
103
103
|
return { success: false, error: "Prompt is required" };
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
// Auth
|
|
107
|
-
|
|
108
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
109
|
-
console.log("[TextToImage] Auth required");
|
|
110
|
-
}
|
|
111
|
-
// Pass retry callback to resume generation after auth
|
|
112
|
-
callbacks.onAuthRequired?.(() => {
|
|
113
|
-
void handleGenerate();
|
|
114
|
-
});
|
|
115
|
-
return { success: false, error: "Authentication required" };
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// Credit check BEFORE generation
|
|
119
|
-
if (!callbacks.canAfford(totalCost)) {
|
|
120
|
-
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
121
|
-
console.log("[TextToImage] Insufficient credits", { totalCost });
|
|
122
|
-
}
|
|
123
|
-
callbacks.onCreditsRequired?.(totalCost);
|
|
124
|
-
return { success: false, error: "Insufficient credits" };
|
|
125
|
-
}
|
|
106
|
+
// Auth and credit checks should be handled by useFeatureGate before calling this
|
|
107
|
+
// Only keeping prompt validation here
|
|
126
108
|
|
|
127
109
|
const request: TextToImageGenerationRequest = {
|
|
128
110
|
prompt: trimmedPrompt,
|
|
@@ -144,7 +126,7 @@ export function useGeneration(options: UseGenerationOptions): UseGenerationRetur
|
|
|
144
126
|
|
|
145
127
|
// Return result based on orchestrator state
|
|
146
128
|
return null; // Result handled via callbacks
|
|
147
|
-
}, [formState, generate, callbacks
|
|
129
|
+
}, [formState, generate, callbacks]);
|
|
148
130
|
|
|
149
131
|
return {
|
|
150
132
|
generationState: {
|