@umituz/react-native-ai-generation-content 1.27.10 → 1.27.12

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.27.10",
3
+ "version": "1.27.12",
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",
@@ -5,17 +5,39 @@
5
5
 
6
6
  /** Step Types */
7
7
  export enum StepType {
8
+ // Gate steps - auth and credits check
9
+ AUTH_GATE = "auth_gate",
10
+ CREDIT_GATE = "credit_gate",
11
+ // Content steps
8
12
  CATEGORY_SELECTION = "category_selection",
9
13
  SCENARIO_SELECTION = "scenario_selection",
10
14
  SCENARIO_PREVIEW = "scenario_preview",
11
15
  PARTNER_UPLOAD = "partner_upload",
12
16
  TEXT_INPUT = "text_input",
13
17
  FEATURE_SELECTION = "feature_selection",
18
+ // Generation steps
14
19
  GENERATING = "generating",
15
20
  RESULT_PREVIEW = "result_preview",
16
21
  CUSTOM = "custom",
17
22
  }
18
23
 
24
+ /** Gate Step Result */
25
+ export type GateResult = "passed" | "blocked" | "pending";
26
+
27
+ /** Auth Gate Configuration */
28
+ export interface AuthGateConfig {
29
+ readonly allowAnonymous?: boolean;
30
+ readonly onAuthRequired?: () => void;
31
+ readonly onAuthSuccess?: () => void;
32
+ }
33
+
34
+ /** Credit Gate Configuration */
35
+ export interface CreditGateConfig {
36
+ readonly requiredCredits: number;
37
+ readonly onCreditsExhausted?: () => void;
38
+ readonly onCreditsAvailable?: () => void;
39
+ }
40
+
19
41
  /** Partner Configuration */
20
42
  export interface PartnerConfig {
21
43
  readonly partnerId: "A" | "B" | "single";
@@ -7,11 +7,18 @@
7
7
  // Feature Type Enums
8
8
  // ============================================================================
9
9
 
10
- export type GenerationType = "image" | "video" | "meme";
10
+ export type GenerationType =
11
+ | "image"
12
+ | "video"
13
+ | "text-to-image"
14
+ | "text-to-video"
15
+ | "image-to-video"
16
+ | "meme";
11
17
 
12
18
  export type InputType =
13
19
  | "single-photo"
14
20
  | "two-photos"
21
+ | "text-only"
15
22
  | "text"
16
23
  | "photo-text"
17
24
  | "photo-photo-text";
@@ -33,6 +33,22 @@ export interface ImageGenerationInput {
33
33
  outputFormat?: "jpeg" | "png" | "webp";
34
34
  }
35
35
 
36
+ export interface TextToImageInput {
37
+ prompt: string;
38
+ negativePrompt?: string;
39
+ aspectRatio?: string;
40
+ size?: string;
41
+ numImages?: number;
42
+ guidanceScale?: number;
43
+ style?: string;
44
+ outputFormat?: "jpeg" | "png" | "webp";
45
+ }
46
+
47
+ export interface TextToImageOutput {
48
+ imageUrl: string;
49
+ imageUrls: string[];
50
+ }
51
+
36
52
  export interface VideoGenerationInput {
37
53
  sourceImageBase64: string;
38
54
  targetImageBase64?: string;
@@ -27,7 +27,24 @@ export type {
27
27
  VideoGenerationOutput,
28
28
  MemeGenerationInput,
29
29
  MemeGenerationOutput,
30
+ TextToImageInput,
31
+ TextToImageOutput,
30
32
  } from "./domain/generation.types";
31
33
 
34
+ export { ExecutorFactory, type GenerationType as ExecutorGenerationType } from "./infrastructure/executors/executor-factory";
35
+
32
36
  export * from "./wizard";
33
37
  export * from "./infrastructure/flow";
38
+
39
+ // Flow config types from domain
40
+ export {
41
+ StepType,
42
+ type GateResult,
43
+ type AuthGateConfig,
44
+ type CreditGateConfig,
45
+ type FlowState,
46
+ type FlowActions,
47
+ type FlowCallbacks,
48
+ type FlowConfiguration,
49
+ type StepDefinition,
50
+ } from "../../domain/entities/flow-config.types";
@@ -6,10 +6,11 @@
6
6
  import type { GenerationExecutor } from "../../domain/generation.types";
7
7
  import { ImageExecutor } from "./image-executor";
8
8
  import { VideoExecutor } from "./video-executor";
9
+ import { TextToImageExecutor } from "./text-to-image-executor";
9
10
 
10
11
  declare const __DEV__: boolean;
11
12
 
12
- type GenerationType = "image" | "video" | "meme";
13
+ export type GenerationType = "image" | "video" | "text-to-image" | "text-to-video" | "image-to-video" | "meme";
13
14
 
14
15
  export class ExecutorFactory {
15
16
  private static executors = new Map<
@@ -27,7 +28,12 @@ export class ExecutorFactory {
27
28
  case "image":
28
29
  this.executors.set(type, new ImageExecutor());
29
30
  break;
31
+ case "text-to-image":
32
+ this.executors.set(type, new TextToImageExecutor());
33
+ break;
30
34
  case "video":
35
+ case "text-to-video":
36
+ case "image-to-video":
31
37
  this.executors.set(type, new VideoExecutor());
32
38
  break;
33
39
  case "meme":
@@ -0,0 +1,173 @@
1
+ /**
2
+ * Text-to-Image Executor
3
+ * Uses provider.run() for quick prompt-based image generation
4
+ */
5
+
6
+ import type {
7
+ GenerationExecutor,
8
+ GenerationOptions,
9
+ GenerationResult,
10
+ } from "../../domain/generation.types";
11
+ import { providerRegistry } from "../../../../infrastructure/services/provider-registry.service";
12
+
13
+ declare const __DEV__: boolean;
14
+
15
+ export interface TextToImageInput {
16
+ prompt: string;
17
+ negativePrompt?: string;
18
+ aspectRatio?: string;
19
+ size?: string;
20
+ numImages?: number;
21
+ guidanceScale?: number;
22
+ style?: string;
23
+ outputFormat?: "jpeg" | "png" | "webp";
24
+ }
25
+
26
+ export interface TextToImageOutput {
27
+ imageUrl: string;
28
+ imageUrls: string[];
29
+ }
30
+
31
+ export class TextToImageExecutor
32
+ implements GenerationExecutor<TextToImageInput, TextToImageOutput>
33
+ {
34
+ async generate(
35
+ model: string,
36
+ input: TextToImageInput,
37
+ options?: GenerationOptions,
38
+ ): Promise<GenerationResult<TextToImageOutput>> {
39
+ try {
40
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
41
+ console.log("[TextToImageExecutor] Starting", {
42
+ model,
43
+ promptLength: input.prompt?.length || 0,
44
+ aspectRatio: input.aspectRatio,
45
+ numImages: input.numImages,
46
+ });
47
+ }
48
+
49
+ const provider = providerRegistry.getActiveProvider();
50
+
51
+ if (!provider?.isInitialized()) {
52
+ return { success: false, error: "AI provider not initialized" };
53
+ }
54
+
55
+ if (!input.prompt?.trim()) {
56
+ return { success: false, error: "Prompt is required" };
57
+ }
58
+
59
+ options?.onProgress?.(10);
60
+
61
+ const modelInput = this.buildModelInput(input);
62
+ options?.onProgress?.(20);
63
+
64
+ const result = await provider.run(model, modelInput);
65
+ options?.onProgress?.(90);
66
+
67
+ const extracted = this.extractResult(result);
68
+ options?.onProgress?.(100);
69
+
70
+ if (!extracted?.imageUrl) {
71
+ return { success: false, error: "No image in response" };
72
+ }
73
+
74
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
75
+ console.log("[TextToImageExecutor] Success", {
76
+ imageCount: extracted.imageUrls.length,
77
+ });
78
+ }
79
+
80
+ return { success: true, data: extracted };
81
+ } catch (error) {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+
84
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
85
+ console.error("[TextToImageExecutor] Error:", message);
86
+ }
87
+
88
+ return { success: false, error: message };
89
+ }
90
+ }
91
+
92
+ private buildModelInput(input: TextToImageInput): Record<string, unknown> {
93
+ const {
94
+ prompt,
95
+ negativePrompt,
96
+ aspectRatio,
97
+ size,
98
+ numImages,
99
+ guidanceScale,
100
+ style,
101
+ outputFormat,
102
+ } = input;
103
+
104
+ let finalPrompt = prompt;
105
+ if (style && style !== "none") {
106
+ finalPrompt = `${prompt}, ${style} style`;
107
+ }
108
+
109
+ return {
110
+ prompt: finalPrompt,
111
+ ...(negativePrompt && { negative_prompt: negativePrompt }),
112
+ ...(aspectRatio && { aspect_ratio: aspectRatio }),
113
+ ...(size && { image_size: size }),
114
+ ...(numImages && { num_images: numImages }),
115
+ ...(guidanceScale && { guidance_scale: guidanceScale }),
116
+ ...(outputFormat && { output_format: outputFormat }),
117
+ };
118
+ }
119
+
120
+ private extractResult(
121
+ result: unknown,
122
+ ): { imageUrl: string; imageUrls: string[] } | undefined {
123
+ if (typeof result !== "object" || result === null) {
124
+ return undefined;
125
+ }
126
+
127
+ const r = result as Record<string, unknown>;
128
+
129
+ // Check nested 'data' object
130
+ if (r.data && typeof r.data === "object") {
131
+ const urls = this.extractImagesFromObject(r.data as Record<string, unknown>);
132
+ if (urls.length > 0) {
133
+ return { imageUrl: urls[0], imageUrls: urls };
134
+ }
135
+ }
136
+
137
+ // Check direct images array
138
+ const directUrls = this.extractImagesFromObject(r);
139
+ if (directUrls.length > 0) {
140
+ return { imageUrl: directUrls[0], imageUrls: directUrls };
141
+ }
142
+
143
+ // Check for imageUrl
144
+ if (typeof r.imageUrl === "string") {
145
+ return { imageUrl: r.imageUrl, imageUrls: [r.imageUrl] };
146
+ }
147
+
148
+ // Fallback: base64
149
+ if (typeof r.imageBase64 === "string") {
150
+ const mimeType = typeof r.mimeType === "string" ? r.mimeType : "image/png";
151
+ const dataUrl = `data:${mimeType};base64,${r.imageBase64}`;
152
+ return { imageUrl: dataUrl, imageUrls: [dataUrl] };
153
+ }
154
+
155
+ return undefined;
156
+ }
157
+
158
+ private extractImagesFromObject(obj: Record<string, unknown>): string[] {
159
+ if (!Array.isArray(obj.images)) {
160
+ return [];
161
+ }
162
+
163
+ return obj.images
164
+ .map((img) => {
165
+ if (typeof img === "string") return img;
166
+ if (img && typeof img === "object" && "url" in img) {
167
+ return (img as { url: string }).url;
168
+ }
169
+ return null;
170
+ })
171
+ .filter((url): url is string => url !== null);
172
+ }
173
+ }
@@ -72,10 +72,32 @@ export interface PreviewStepConfig extends BaseStepConfig {
72
72
  readonly showContinueButton?: boolean;
73
73
  }
74
74
 
75
+ /**
76
+ * Auth Gate Step Configuration
77
+ * Blocks flow if user is not authenticated
78
+ */
79
+ export interface AuthGateStepConfig extends BaseStepConfig {
80
+ readonly type: "auth_gate";
81
+ readonly allowAnonymous?: boolean;
82
+ readonly messageKey?: string;
83
+ }
84
+
85
+ /**
86
+ * Credit Gate Step Configuration
87
+ * Blocks flow if user doesn't have enough credits
88
+ */
89
+ export interface CreditGateStepConfig extends BaseStepConfig {
90
+ readonly type: "credit_gate";
91
+ readonly requiredCredits: number;
92
+ readonly messageKey?: string;
93
+ }
94
+
75
95
  /**
76
96
  * Union of all step config types
77
97
  */
78
98
  export type WizardStepConfig =
99
+ | AuthGateStepConfig
100
+ | CreditGateStepConfig
79
101
  | PhotoUploadStepConfig
80
102
  | TextInputStepConfig
81
103
  | SelectionStepConfig
@@ -7,6 +7,8 @@
7
7
  // Domain Entities - Configuration Types
8
8
  export type {
9
9
  BaseStepConfig,
10
+ AuthGateStepConfig,
11
+ CreditGateStepConfig,
10
12
  PhotoUploadStepConfig,
11
13
  TextInputStepConfig,
12
14
  SelectionStepConfig,
@@ -0,0 +1,138 @@
1
+ /**
2
+ * useGateStep Hook
3
+ * Handles auth and credit gate steps in wizard flow
4
+ *
5
+ * Gates are "invisible" steps that:
6
+ * - Auto-advance when condition is met
7
+ * - Show modal/paywall when blocked
8
+ * - Never show UI themselves
9
+ */
10
+
11
+ import { useEffect, useRef, useCallback } from "react";
12
+ import type { GateResult } from "../../../../../domain/entities/flow-config.types";
13
+
14
+ declare const __DEV__: boolean;
15
+
16
+ export interface GateCallbacks {
17
+ /** Called when auth is required - should show auth modal */
18
+ readonly onAuthRequired?: () => void;
19
+ /** Called when credits are exhausted - should show paywall */
20
+ readonly onCreditsExhausted?: () => void;
21
+ /** Called when gate passes - auto advance to next step */
22
+ readonly onGatePassed?: () => void;
23
+ }
24
+
25
+ export interface AuthGateState {
26
+ readonly isAuthenticated: boolean;
27
+ readonly isAnonymous: boolean;
28
+ readonly userId: string | null;
29
+ }
30
+
31
+ export interface CreditGateState {
32
+ readonly creditBalance: number;
33
+ readonly requiredCredits: number;
34
+ }
35
+
36
+ export interface UseGateStepProps {
37
+ readonly gateType: "auth" | "credit";
38
+ readonly authState?: AuthGateState;
39
+ readonly creditState?: CreditGateState;
40
+ readonly callbacks: GateCallbacks;
41
+ readonly allowAnonymous?: boolean;
42
+ }
43
+
44
+ export interface UseGateStepReturn {
45
+ readonly gateResult: GateResult;
46
+ readonly checkGate: () => GateResult;
47
+ }
48
+
49
+ /**
50
+ * Hook to manage gate step logic
51
+ * Automatically triggers callbacks and advances when conditions are met
52
+ */
53
+ export function useGateStep(props: UseGateStepProps): UseGateStepReturn {
54
+ const { gateType, authState, creditState, callbacks, allowAnonymous = false } = props;
55
+
56
+ const hasChecked = useRef(false);
57
+
58
+ const checkAuthGate = useCallback((): GateResult => {
59
+ if (!authState) return "pending";
60
+
61
+ const { isAuthenticated, isAnonymous, userId } = authState;
62
+
63
+ // Allow anonymous if configured
64
+ if (allowAnonymous && userId) {
65
+ return "passed";
66
+ }
67
+
68
+ // Must be authenticated and not anonymous
69
+ if (isAuthenticated && !isAnonymous && userId) {
70
+ return "passed";
71
+ }
72
+
73
+ return "blocked";
74
+ }, [authState, allowAnonymous]);
75
+
76
+ const checkCreditGate = useCallback((): GateResult => {
77
+ if (!creditState) return "pending";
78
+
79
+ const { creditBalance, requiredCredits } = creditState;
80
+
81
+ if (creditBalance >= requiredCredits) {
82
+ return "passed";
83
+ }
84
+
85
+ return "blocked";
86
+ }, [creditState]);
87
+
88
+ const checkGate = useCallback((): GateResult => {
89
+ if (gateType === "auth") {
90
+ return checkAuthGate();
91
+ }
92
+ return checkCreditGate();
93
+ }, [gateType, checkAuthGate, checkCreditGate]);
94
+
95
+ // Auto-check and trigger callbacks on mount
96
+ useEffect(() => {
97
+ if (hasChecked.current) return;
98
+
99
+ const result = checkGate();
100
+
101
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
102
+ console.log(`[useGateStep] ${gateType} gate check:`, result);
103
+ }
104
+
105
+ if (result === "passed") {
106
+ hasChecked.current = true;
107
+ callbacks.onGatePassed?.();
108
+ } else if (result === "blocked") {
109
+ hasChecked.current = true;
110
+ if (gateType === "auth") {
111
+ callbacks.onAuthRequired?.();
112
+ } else {
113
+ callbacks.onCreditsExhausted?.();
114
+ }
115
+ }
116
+ }, [gateType, checkGate, callbacks]);
117
+
118
+ // Re-check when auth/credit state changes
119
+ useEffect(() => {
120
+ if (!hasChecked.current) return;
121
+
122
+ const result = checkGate();
123
+
124
+ if (result === "passed") {
125
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
126
+ console.log(`[useGateStep] ${gateType} gate now passed, advancing...`);
127
+ }
128
+ callbacks.onGatePassed?.();
129
+ }
130
+ }, [authState, creditState, gateType, checkGate, callbacks]);
131
+
132
+ return {
133
+ gateResult: checkGate(),
134
+ checkGate,
135
+ };
136
+ }
137
+
138
+ export default useGateStep;
@@ -39,6 +39,8 @@ export type TextToImageGenerationResult =
39
39
  | TextToImageGenerationResultError;
40
40
 
41
41
  export interface TextToImageCallbacks {
42
+ /** User ID for orchestrator - required for credit deduction */
43
+ userId: string | null;
42
44
  executeGeneration: (
43
45
  request: TextToImageGenerationRequest,
44
46
  ) => Promise<TextToImageGenerationResult>;
@@ -46,7 +46,10 @@ const DEFAULT_ALERT_MESSAGES: AlertMessages = {
46
46
  };
47
47
 
48
48
  export function useGeneration(options: UseGenerationOptions): UseGenerationReturn {
49
- const { formState, callbacks, userId, onPromptCleared } = options;
49
+ const { formState, callbacks, onPromptCleared } = options;
50
+
51
+ // Get userId from callbacks (from app layer via useAIFeatureCallbacks)
52
+ const userId = callbacks.userId ?? undefined;
50
53
 
51
54
  const totalCost = callbacks.calculateCost(formState.numImages, formState.selectedModel);
52
55
 
@@ -100,6 +103,24 @@ export function useGeneration(options: UseGenerationOptions): UseGenerationRetur
100
103
  return { success: false, error: "Prompt is required" };
101
104
  }
102
105
 
106
+ // Auth check BEFORE generation
107
+ if (!callbacks.isAuthenticated()) {
108
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
109
+ console.log("[TextToImage] Auth required");
110
+ }
111
+ callbacks.onAuthRequired?.();
112
+ return { success: false, error: "Authentication required" };
113
+ }
114
+
115
+ // Credit check BEFORE generation
116
+ if (!callbacks.canAfford(totalCost)) {
117
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
118
+ console.log("[TextToImage] Insufficient credits", { totalCost });
119
+ }
120
+ callbacks.onCreditsRequired?.(totalCost);
121
+ return { success: false, error: "Insufficient credits" };
122
+ }
123
+
103
124
  const request: TextToImageGenerationRequest = {
104
125
  prompt: trimmedPrompt,
105
126
  model: formState.selectedModel ?? undefined,
@@ -120,7 +141,7 @@ export function useGeneration(options: UseGenerationOptions): UseGenerationRetur
120
141
 
121
142
  // Return result based on orchestrator state
122
143
  return null; // Result handled via callbacks
123
- }, [formState, generate, callbacks]);
144
+ }, [formState, generate, callbacks, totalCost]);
124
145
 
125
146
  return {
126
147
  generationState: {
@@ -86,13 +86,20 @@ export const GenerateButton: React.FC<GenerateButtonProps> = ({
86
86
  >
87
87
  <View style={styles.buttonContent}>
88
88
  {isProcessing ? (
89
- <AtomicSpinner size="sm" color={tokens.colors.textInverse} />
89
+ <AtomicSpinner size="sm" color={disabled ? tokens.colors.textSecondary : tokens.colors.textInverse} />
90
90
  ) : (
91
- <AtomicIcon name={icon} customSize={iconSize} customColor={tokens.colors.textInverse} />
91
+ <AtomicIcon
92
+ name={icon}
93
+ customSize={iconSize}
94
+ customColor={disabled ? tokens.colors.textSecondary : tokens.colors.textInverse}
95
+ />
92
96
  )}
93
97
  <AtomicText
94
98
  type="bodyLarge"
95
- style={[styles.buttonText, { color: tokens.colors.textInverse }]}
99
+ style={[
100
+ styles.buttonText,
101
+ { color: disabled ? tokens.colors.textSecondary : tokens.colors.textInverse }
102
+ ]}
96
103
  >
97
104
  {finalDisplayText}
98
105
  </AtomicText>
@@ -45,6 +45,9 @@ export type AIFeatureGenerationResult =
45
45
  * Universal callbacks interface that maps to all feature-specific ones
46
46
  */
47
47
  export interface AIFeatureCallbacks<TRequest = unknown, TResult = unknown> {
48
+ // User state - needed by orchestrator
49
+ userId: string | null;
50
+
48
51
  // TextToImageCallbacks compatible
49
52
  executeGeneration: (request: TRequest) => Promise<AIFeatureGenerationResult>;
50
53
  calculateCost: (multiplier?: number, _model?: string | null) => number;
@@ -140,6 +143,7 @@ export function useAIFeatureCallbacks<TRequest = unknown, TResult = unknown>(
140
143
 
141
144
  return useMemo(
142
145
  () => ({
146
+ userId,
143
147
  executeGeneration,
144
148
  calculateCost,
145
149
  canAfford,
@@ -153,6 +157,7 @@ export function useAIFeatureCallbacks<TRequest = unknown, TResult = unknown>(
153
157
  onShowPaywall,
154
158
  }),
155
159
  [
160
+ userId,
156
161
  executeGeneration,
157
162
  calculateCost,
158
163
  canAfford,