@umituz/react-native-ai-generation-content 1.72.0 → 1.72.1

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.0",
3
+ "version": "1.72.1",
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",
@@ -99,9 +99,36 @@ const personalInfoRules: ModerationRule[] = [
99
99
  },
100
100
  ];
101
101
 
102
+ const intimateContentRules: ModerationRule[] = [
103
+ {
104
+ id: "intimate-001",
105
+ name: "Romantic and Intimate Content",
106
+ description: "Detects romantic and intimate content requests",
107
+ contentTypes: ["text"],
108
+ severity: "high",
109
+ violationType: "explicit_content",
110
+ patterns: [
111
+ "\\bkiss(ing|es|ed)?\\b",
112
+ "\\bhug(ging|s|ged)?\\b",
113
+ "\\bromantic\\b",
114
+ "\\bintimate\\b",
115
+ "\\bcouple\\b.*\\b(kissing|hugging|embracing)\\b",
116
+ "\\b(kissing|hugging|embracing)\\b.*\\bcouple\\b",
117
+ "\\blove\\b.*\\bscene\\b",
118
+ "\\bsensual\\b",
119
+ "\\bseductive\\b",
120
+ "\\bpassionate\\b.*\\b(kiss|embrace)\\b",
121
+ "\\btwo people\\b.*\\b(kissing|hugging|embracing)\\b",
122
+ "\\bpeople\\b.*\\b(kissing|hugging|embracing)\\b",
123
+ ],
124
+ enabled: true,
125
+ },
126
+ ];
127
+
102
128
  export const defaultModerationRules: ModerationRule[] = [
103
129
  ...violenceRules,
104
130
  ...hateSpeechRules,
105
131
  ...illegalActivityRules,
106
132
  ...personalInfoRules,
133
+ ...intimateContentRules,
107
134
  ];
@@ -13,7 +13,7 @@ import type {
13
13
  export type { ModerationResult };
14
14
 
15
15
  const DEFAULT_SUGGESTIONS: Record<string, string> = {
16
- explicit_content: "Remove explicit content",
16
+ explicit_content: "Romantic and intimate content is not supported. Please try creating content with single subjects or non-intimate scenarios.",
17
17
  violence: "Remove violent content",
18
18
  hate_speech: "Remove discriminatory language",
19
19
  harassment: "Remove harassing content",
@@ -4,7 +4,7 @@
4
4
  * Uses design system's useMedia hook for media picking with built-in validation
5
5
  */
6
6
 
7
- import { useState, useCallback, useEffect } from "react";
7
+ import { useState, useCallback, useEffect, useRef } from "react";
8
8
  import { useMedia, MediaQuality, MediaValidationError, MEDIA_CONSTANTS } from "@umituz/react-native-design-system";
9
9
  import type { UploadedImage } from "../../../../../presentation/hooks/generation/useAIGenerateState";
10
10
 
@@ -49,10 +49,10 @@ export const usePhotoUploadState = ({
49
49
  }: UsePhotoUploadStateProps): UsePhotoUploadStateReturn => {
50
50
  const [image, setImage] = useState<UploadedImage | null>(initialImage || null);
51
51
  const { pickImage, isLoading } = useMedia();
52
+ const timeoutRef = useRef<NodeJS.Timeout | undefined>(undefined);
52
53
 
53
54
  const maxFileSizeMB = config?.maxFileSizeMB ?? MEDIA_CONSTANTS.MAX_IMAGE_SIZE_MB;
54
55
 
55
- // Reset state when stepId changes (new step = new photo)
56
56
  useEffect(() => {
57
57
  if (typeof __DEV__ !== "undefined" && __DEV__) {
58
58
  console.log("[usePhotoUploadState] Step changed, resetting image", { stepId, hasInitialImage: !!initialImage });
@@ -60,12 +60,40 @@ export const usePhotoUploadState = ({
60
60
  setImage(initialImage || null);
61
61
  }, [stepId, initialImage]);
62
62
 
63
+ useEffect(() => {
64
+ if (isLoading) {
65
+ timeoutRef.current = setTimeout(() => {
66
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
67
+ console.warn("[usePhotoUploadState] Image picker timeout - possible stuck state");
68
+ }
69
+ onError?.({
70
+ title: translations.error,
71
+ message: "Image selection is taking too long. Please try again.",
72
+ });
73
+ }, 30000);
74
+ } else {
75
+ if (timeoutRef.current) {
76
+ clearTimeout(timeoutRef.current);
77
+ }
78
+ }
79
+
80
+ return () => {
81
+ if (timeoutRef.current) {
82
+ clearTimeout(timeoutRef.current);
83
+ }
84
+ };
85
+ }, [isLoading, onError, translations]);
86
+
63
87
  const clearImage = useCallback(() => {
64
88
  setImage(null);
65
89
  }, []);
66
90
 
67
91
  const handlePickImage = useCallback(async () => {
68
92
  try {
93
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
94
+ console.log("[usePhotoUploadState] Starting image pick");
95
+ }
96
+
69
97
  const result = await pickImage({
70
98
  allowsEditing: true,
71
99
  aspect: [1, 1],
@@ -73,8 +101,11 @@ export const usePhotoUploadState = ({
73
101
  maxFileSizeMB,
74
102
  });
75
103
 
76
- // Handle validation errors from design system
77
104
  if (result.error) {
105
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
106
+ console.log("[usePhotoUploadState] Validation error", result.error);
107
+ }
108
+
78
109
  if (result.error === MediaValidationError.FILE_TOO_LARGE) {
79
110
  onError?.({
80
111
  title: translations.fileTooLarge,
@@ -90,6 +121,9 @@ export const usePhotoUploadState = ({
90
121
  }
91
122
 
92
123
  if (result.canceled || !result.assets || result.assets.length === 0) {
124
+ if (typeof __DEV__ !== "undefined" && __DEV__) {
125
+ console.log("[usePhotoUploadState] Image pick canceled");
126
+ }
93
127
  return;
94
128
  }
95
129
 
@@ -13,8 +13,12 @@ import {
13
13
  ScreenLayout,
14
14
  NavigationHeader,
15
15
  type DesignTokens,
16
+ useAlert,
17
+ AlertType,
18
+ AlertMode,
16
19
  } from "@umituz/react-native-design-system";
17
20
  import { ContinueButton } from "../../../../../presentation/components/buttons";
21
+ import { contentModerationService } from "../../../../../domains/content-moderation";
18
22
  import type { TextInputScreenProps } from "./TextInputScreen.types";
19
23
 
20
24
  export type {
@@ -33,17 +37,36 @@ export const TextInputScreen: React.FC<TextInputScreenProps> = ({
33
37
  onContinue,
34
38
  }) => {
35
39
  const tokens = useAppDesignTokens();
40
+ const alert = useAlert();
36
41
  const [text, setText] = useState(initialValue);
37
42
 
38
43
  const minLength = config?.minLength ?? 3;
39
44
  const maxLength = config?.maxLength ?? 1000;
40
45
  const canContinue = text.trim().length >= minLength;
41
46
 
42
- const handleContinue = useCallback(() => {
43
- if (canContinue) {
44
- onContinue(text.trim());
47
+ const handleContinue = useCallback(async () => {
48
+ if (!canContinue) return;
49
+
50
+ const trimmedText = text.trim();
51
+
52
+ const moderationResult = await contentModerationService.moderate({
53
+ contentType: "text",
54
+ content: trimmedText,
55
+ });
56
+
57
+ if (!moderationResult.isAllowed) {
58
+ const violation = moderationResult.violations[0];
59
+ alert.show(
60
+ AlertType.ERROR,
61
+ AlertMode.MODAL,
62
+ translations.contentNotAllowed || "Content Not Allowed",
63
+ violation?.suggestion || translations.contentNotAllowedMessage || "This type of content is not supported. Please try a different prompt."
64
+ );
65
+ return;
45
66
  }
46
- }, [canContinue, text, onContinue]);
67
+
68
+ onContinue(trimmedText);
69
+ }, [canContinue, text, onContinue, alert, translations]);
47
70
 
48
71
  const handleExampleSelect = useCallback((example: string) => {
49
72
  setText(example);
@@ -9,6 +9,8 @@ export interface TextInputScreenTranslations {
9
9
  readonly continueButton: string;
10
10
  readonly backButton?: string;
11
11
  readonly examplesTitle?: string;
12
+ readonly contentNotAllowed?: string;
13
+ readonly contentNotAllowedMessage?: string;
12
14
  }
13
15
 
14
16
  export interface TextInputScreenConfig {