@umituz/react-native-ai-generation-content 1.26.63 → 1.26.65
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/infrastructure/strategies/image-generation.strategy.ts +10 -1
- package/src/domains/generation/wizard/presentation/components/GenericWizardFlow.tsx +31 -0
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.tsx +4 -0
- package/src/domains/generation/wizard/presentation/components/WizardStepRenderer.types.ts +2 -0
- package/src/domains/prompts/domain/entities/BasePromptStructure.ts +53 -24
- package/src/domains/prompts/index.ts +12 -6
- package/src/domains/prompts/infrastructure/builders/face-preservation-builder.ts +114 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.26.
|
|
3
|
+
"version": "1.26.65",
|
|
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",
|
package/src/domains/generation/wizard/infrastructure/strategies/image-generation.strategy.ts
CHANGED
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
DEFAULT_STYLE_VALUE,
|
|
15
15
|
MODEL_INPUT_DEFAULTS,
|
|
16
16
|
} from "./wizard-strategy.constants";
|
|
17
|
+
import { buildFacePreservationPrompt } from "../../../../prompts/infrastructure/builders/face-preservation-builder";
|
|
17
18
|
|
|
18
19
|
declare const __DEV__: boolean;
|
|
19
20
|
|
|
@@ -86,7 +87,15 @@ async function executeImageGeneration(
|
|
|
86
87
|
return { success: false, error: "At least one image required" };
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
|
|
90
|
+
// Build face preservation prompt dynamically based on number of people
|
|
91
|
+
const enhancedPrompt = buildFacePreservationPrompt({
|
|
92
|
+
scenarioPrompt: input.prompt,
|
|
93
|
+
personCount: imageUrls.length,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
if (typeof __DEV__ !== "undefined" && __DEV__) {
|
|
97
|
+
console.log("[ImageStrategy] Face preservation prompt for", imageUrls.length, "person(s)");
|
|
98
|
+
}
|
|
90
99
|
|
|
91
100
|
const modelInput = {
|
|
92
101
|
image_urls: imageUrls,
|
|
@@ -26,6 +26,7 @@ import type { Creation } from "../../../../creations/domain/entities/Creation";
|
|
|
26
26
|
import { useResultActions } from "../../../../result-preview/presentation/hooks/useResultActions";
|
|
27
27
|
import { validateScenario } from "../utilities/validateScenario";
|
|
28
28
|
import { WizardStepRenderer } from "./WizardStepRenderer";
|
|
29
|
+
import { StarRatingPicker } from "../../../../result-preview/presentation/components/StarRatingPicker";
|
|
29
30
|
|
|
30
31
|
export interface GenericWizardFlowProps {
|
|
31
32
|
readonly featureConfig: WizardFeatureConfig;
|
|
@@ -37,6 +38,7 @@ export interface GenericWizardFlowProps {
|
|
|
37
38
|
readonly onGenerationComplete?: (result: unknown) => void;
|
|
38
39
|
readonly onGenerationError?: (error: string) => void;
|
|
39
40
|
readonly onCreditsExhausted?: () => void;
|
|
41
|
+
readonly onRate?: (creationId: string, rating: number, description: string) => Promise<boolean>;
|
|
40
42
|
readonly onBack?: () => void;
|
|
41
43
|
readonly onTryAgain?: () => void;
|
|
42
44
|
readonly t: (key: string) => string;
|
|
@@ -56,6 +58,7 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
56
58
|
onGenerationComplete,
|
|
57
59
|
onGenerationError,
|
|
58
60
|
onCreditsExhausted,
|
|
61
|
+
onRate,
|
|
59
62
|
onBack,
|
|
60
63
|
onTryAgain,
|
|
61
64
|
t,
|
|
@@ -66,6 +69,8 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
66
69
|
}) => {
|
|
67
70
|
const tokens = useAppDesignTokens();
|
|
68
71
|
const [currentCreation, setCurrentCreation] = useState<Creation | null>(null);
|
|
72
|
+
const [showRatingPicker, setShowRatingPicker] = useState(false);
|
|
73
|
+
const [hasRated, setHasRated] = useState(false);
|
|
69
74
|
const prevStepIdRef = useRef<string | undefined>(undefined);
|
|
70
75
|
|
|
71
76
|
const flowSteps = useMemo<StepDefinition[]>(() => {
|
|
@@ -155,6 +160,21 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
155
160
|
nextStep();
|
|
156
161
|
}, [currentStepIndex, flowSteps.length, customData, setCustomData, nextStep, onGenerationStart]);
|
|
157
162
|
|
|
163
|
+
const handleOpenRatingPicker = useCallback(() => {
|
|
164
|
+
setShowRatingPicker(true);
|
|
165
|
+
}, []);
|
|
166
|
+
|
|
167
|
+
const handleSubmitRating = useCallback(async (rating: number, description: string) => {
|
|
168
|
+
if (!currentCreation?.id || !onRate) return;
|
|
169
|
+
const success = await onRate(currentCreation.id, rating, description);
|
|
170
|
+
if (success) {
|
|
171
|
+
setHasRated(true);
|
|
172
|
+
}
|
|
173
|
+
setShowRatingPicker(false);
|
|
174
|
+
}, [currentCreation, onRate]);
|
|
175
|
+
|
|
176
|
+
const showRatingButton = Boolean(onRate) && !hasRated;
|
|
177
|
+
|
|
158
178
|
return (
|
|
159
179
|
<View style={[styles.container, { backgroundColor: tokens.colors.backgroundPrimary }]}>
|
|
160
180
|
<WizardStepRenderer
|
|
@@ -165,17 +185,28 @@ export const GenericWizardFlow: React.FC<GenericWizardFlowProps> = ({
|
|
|
165
185
|
generationResult={generationResult}
|
|
166
186
|
isSaving={isSaving}
|
|
167
187
|
isSharing={isSharing}
|
|
188
|
+
showRating={showRatingButton}
|
|
168
189
|
onNext={nextStep}
|
|
169
190
|
onBack={handleBack}
|
|
170
191
|
onPhotoContinue={handlePhotoContinue}
|
|
171
192
|
onDownload={handleDownload}
|
|
172
193
|
onShare={handleShare}
|
|
194
|
+
onRate={handleOpenRatingPicker}
|
|
173
195
|
onTryAgain={onTryAgain}
|
|
174
196
|
t={t}
|
|
175
197
|
renderPreview={renderPreview}
|
|
176
198
|
renderGenerating={renderGenerating}
|
|
177
199
|
renderResult={renderResult}
|
|
178
200
|
/>
|
|
201
|
+
<StarRatingPicker
|
|
202
|
+
visible={showRatingPicker}
|
|
203
|
+
onClose={() => setShowRatingPicker(false)}
|
|
204
|
+
onRate={handleSubmitRating}
|
|
205
|
+
title={t("result.rateTitle")}
|
|
206
|
+
submitLabel={t("common.submit")}
|
|
207
|
+
cancelLabel={t("common.cancel")}
|
|
208
|
+
descriptionPlaceholder={t("result.feedbackPlaceholder")}
|
|
209
|
+
/>
|
|
179
210
|
</View>
|
|
180
211
|
);
|
|
181
212
|
};
|
|
@@ -18,11 +18,13 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
|
|
|
18
18
|
generationResult,
|
|
19
19
|
isSaving,
|
|
20
20
|
isSharing,
|
|
21
|
+
showRating = true,
|
|
21
22
|
onNext,
|
|
22
23
|
onBack,
|
|
23
24
|
onPhotoContinue,
|
|
24
25
|
onDownload,
|
|
25
26
|
onShare,
|
|
27
|
+
onRate,
|
|
26
28
|
onTryAgain,
|
|
27
29
|
t,
|
|
28
30
|
renderPreview,
|
|
@@ -77,11 +79,13 @@ export const WizardStepRenderer: React.FC<WizardStepRendererProps> = ({
|
|
|
77
79
|
isSharing={isSharing}
|
|
78
80
|
onDownload={onDownload}
|
|
79
81
|
onShare={onShare}
|
|
82
|
+
onRate={onRate}
|
|
80
83
|
onTryAgain={handleTryAgain}
|
|
81
84
|
onNavigateBack={handleTryAgain}
|
|
82
85
|
hideLabel
|
|
83
86
|
iconOnly
|
|
84
87
|
showTryAgain
|
|
88
|
+
showRating={showRating}
|
|
85
89
|
translations={{
|
|
86
90
|
title: t("generation.result.title"),
|
|
87
91
|
saveButton: t("generation.result.save"),
|
|
@@ -10,11 +10,13 @@ export interface WizardStepRendererProps {
|
|
|
10
10
|
readonly generationResult: unknown;
|
|
11
11
|
readonly isSaving: boolean;
|
|
12
12
|
readonly isSharing: boolean;
|
|
13
|
+
readonly showRating?: boolean;
|
|
13
14
|
readonly onNext: () => void;
|
|
14
15
|
readonly onBack: () => void;
|
|
15
16
|
readonly onPhotoContinue: (stepId: string, image: UploadedImage) => void;
|
|
16
17
|
readonly onDownload: () => void;
|
|
17
18
|
readonly onShare: () => void;
|
|
19
|
+
readonly onRate?: () => void;
|
|
18
20
|
readonly onTryAgain?: () => void;
|
|
19
21
|
readonly t: (key: string) => string;
|
|
20
22
|
readonly renderPreview?: (onContinue: () => void) => React.ReactElement | null;
|
|
@@ -12,15 +12,26 @@
|
|
|
12
12
|
/**
|
|
13
13
|
* Core identity preservation instruction
|
|
14
14
|
* This is the foundation for maintaining facial consistency
|
|
15
|
+
* Updated with stricter rules based on AI image generation best practices
|
|
15
16
|
*/
|
|
16
|
-
export const IDENTITY_PRESERVATION_CORE = `CRITICAL IDENTITY PRESERVATION:
|
|
17
|
+
export const IDENTITY_PRESERVATION_CORE = `CRITICAL IDENTITY PRESERVATION (HIGHEST PRIORITY):
|
|
17
18
|
{
|
|
18
|
-
"policy": "
|
|
19
|
-
"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
19
|
+
"policy": "PRESERVE 100% IDENTICAL FACIAL APPEARANCE FROM INPUT",
|
|
20
|
+
"mandatory_rules": [
|
|
21
|
+
"The face must be EXACTLY as it appears in the reference photo - 100% identical",
|
|
22
|
+
"Preserve every facial detail: bone structure, eye shape, eye color, nose shape, lip shape",
|
|
23
|
+
"Maintain natural skin texture with pores, marks, and realistic details",
|
|
24
|
+
"Keep the person instantly recognizable - any deviation is NOT acceptable"
|
|
25
|
+
],
|
|
26
|
+
"forbidden_modifications": [
|
|
27
|
+
"Do NOT change face shape or facial proportions",
|
|
28
|
+
"Do NOT alter eye color, eye shape, or eye spacing",
|
|
29
|
+
"Do NOT modify nose shape, size, or position",
|
|
30
|
+
"Do NOT change lip shape, thickness, or natural expression",
|
|
31
|
+
"Do NOT alter skin tone or smooth skin texture",
|
|
32
|
+
"Do NOT remove natural features like freckles, moles, or wrinkles"
|
|
33
|
+
],
|
|
34
|
+
"verification": "Before output: confirm face matches reference photo with 100% accuracy"
|
|
24
35
|
}`;
|
|
25
36
|
|
|
26
37
|
/**
|
|
@@ -162,29 +173,47 @@ ${existingPrompt}`;
|
|
|
162
173
|
};
|
|
163
174
|
|
|
164
175
|
/**
|
|
165
|
-
*
|
|
166
|
-
* Ensures
|
|
176
|
+
* Multi-person identity preservation rules
|
|
177
|
+
* Ensures all people maintain their identities with strict rules
|
|
178
|
+
* Supports any number of people (1, 2, 3, N)
|
|
167
179
|
*/
|
|
168
|
-
export const
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
180
|
+
export const MULTI_PERSON_PRESERVATION_RULES = {
|
|
181
|
+
requirement: "ALL individuals must have 100% identical facial appearance to their reference photos",
|
|
182
|
+
perPersonRule: "Use EXACTLY the person from @imageN - preserve 100% identical facial features",
|
|
183
|
+
forbidden: [
|
|
184
|
+
"Do NOT swap, mix, or blend facial features between people",
|
|
185
|
+
"Do NOT idealize or beautify any face",
|
|
186
|
+
"Do NOT alter facial proportions or characteristics",
|
|
187
|
+
],
|
|
188
|
+
positioning: "Natural positioning, all looking at camera with natural expressions",
|
|
189
|
+
} as const;
|
|
177
190
|
|
|
178
191
|
/**
|
|
179
|
-
* Creates a
|
|
180
|
-
*
|
|
181
|
-
* @param scenarioPrompt - The scenario description
|
|
182
|
-
* @
|
|
192
|
+
* Creates a multi-person prompt dynamically
|
|
193
|
+
*
|
|
194
|
+
* @param scenarioPrompt - The scenario description
|
|
195
|
+
* @param personCount - Number of people (1, 2, 3, N)
|
|
196
|
+
* @returns Complete prompt with identity preservation for all people
|
|
183
197
|
*/
|
|
184
|
-
export const
|
|
198
|
+
export const createMultiPersonPrompt = (
|
|
199
|
+
scenarioPrompt: string,
|
|
200
|
+
personCount: number,
|
|
201
|
+
): string => {
|
|
202
|
+
const personRefs = Array.from({ length: personCount }, (_, i) =>
|
|
203
|
+
`Person ${i + 1}: @image${i + 1} - preserve 100% facial identity`
|
|
204
|
+
).join("\n ");
|
|
205
|
+
|
|
185
206
|
return `${IDENTITY_PRESERVATION_CORE}
|
|
186
207
|
|
|
187
|
-
${
|
|
208
|
+
MULTI-PERSON IDENTITY PRESERVATION (${personCount} people):
|
|
209
|
+
{
|
|
210
|
+
"requirement": "${MULTI_PERSON_PRESERVATION_RULES.requirement}",
|
|
211
|
+
"references": [
|
|
212
|
+
${personRefs}
|
|
213
|
+
],
|
|
214
|
+
"forbidden": ${JSON.stringify(MULTI_PERSON_PRESERVATION_RULES.forbidden)},
|
|
215
|
+
"positioning": "${MULTI_PERSON_PRESERVATION_RULES.positioning}"
|
|
216
|
+
}
|
|
188
217
|
|
|
189
218
|
${PHOTOREALISTIC_RENDERING}
|
|
190
219
|
|
|
@@ -84,15 +84,21 @@ export type { IdentitySegment, AnimeStyleSegment, QualitySegment } from './domai
|
|
|
84
84
|
export { ImagePromptBuilder, createAnimeSelfiePrompt, createStyleTransferPrompt } from './infrastructure/services/ImagePromptBuilder';
|
|
85
85
|
export type { ImagePromptResult, ImagePromptBuilderOptions, AnimeSelfiePromptResult } from './infrastructure/services/ImagePromptBuilder';
|
|
86
86
|
|
|
87
|
-
export {
|
|
88
|
-
IDENTITY_PRESERVATION_CORE,
|
|
89
|
-
PHOTOREALISTIC_RENDERING,
|
|
90
|
-
NATURAL_POSE_GUIDELINES,
|
|
87
|
+
export {
|
|
88
|
+
IDENTITY_PRESERVATION_CORE,
|
|
89
|
+
PHOTOREALISTIC_RENDERING,
|
|
90
|
+
NATURAL_POSE_GUIDELINES,
|
|
91
91
|
MASTER_BASE_PROMPT,
|
|
92
|
-
|
|
92
|
+
MULTI_PERSON_PRESERVATION_RULES,
|
|
93
93
|
createEnhancedPrompt,
|
|
94
94
|
createTransformationPrompt,
|
|
95
95
|
enhanceExistingPrompt,
|
|
96
|
-
|
|
96
|
+
createMultiPersonPrompt,
|
|
97
97
|
} from './domain/entities/BasePromptStructure';
|
|
98
98
|
export type { CreatePromptOptions } from './domain/entities/BasePromptStructure';
|
|
99
|
+
|
|
100
|
+
export {
|
|
101
|
+
buildFacePreservationPrompt,
|
|
102
|
+
buildMinimalFacePreservationPrompt,
|
|
103
|
+
} from './infrastructure/builders/face-preservation-builder';
|
|
104
|
+
export type { FacePreservationOptions } from './infrastructure/builders/face-preservation-builder';
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Face Preservation Prompt Builder
|
|
3
|
+
* Dynamic prompt builder for AI image generation with strict face identity preservation
|
|
4
|
+
* Supports any number of people (1, 2, 3, N)
|
|
5
|
+
*
|
|
6
|
+
* Based on best practices:
|
|
7
|
+
* - Face identity lock techniques
|
|
8
|
+
* - @imageN reference anchors
|
|
9
|
+
* - Explicit preservation and negative constraints
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// =============================================================================
|
|
13
|
+
// Types
|
|
14
|
+
// =============================================================================
|
|
15
|
+
|
|
16
|
+
export interface FacePreservationOptions {
|
|
17
|
+
/** The scenario/scene description */
|
|
18
|
+
scenarioPrompt: string;
|
|
19
|
+
/** Number of people in the generation */
|
|
20
|
+
personCount: number;
|
|
21
|
+
/** Optional custom preservation rules from main app */
|
|
22
|
+
customRules?: string[];
|
|
23
|
+
/** Optional custom forbidden actions from main app */
|
|
24
|
+
customForbidden?: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Constants
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
const FACE_LOCK_RULES = [
|
|
32
|
+
"Face identity locked - exact facial structure must be preserved",
|
|
33
|
+
"Same jawline, eye shape, eye color, nose, lips, skin tone",
|
|
34
|
+
"No facial morphing, no face swap, no face modification",
|
|
35
|
+
"Identity consistency enabled - high facial similarity required",
|
|
36
|
+
] as const;
|
|
37
|
+
|
|
38
|
+
const FORBIDDEN_ACTIONS = [
|
|
39
|
+
"Do NOT generate a new face",
|
|
40
|
+
"Do NOT modify facial structure or proportions",
|
|
41
|
+
"Do NOT alter skin tone, texture, or natural marks",
|
|
42
|
+
"Do NOT smooth skin or remove natural features",
|
|
43
|
+
"Do NOT change eye color or shape",
|
|
44
|
+
] as const;
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// Builder Functions
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Generate person reference instructions for N people
|
|
52
|
+
*/
|
|
53
|
+
function buildPersonReferences(count: number): string {
|
|
54
|
+
if (count === 1) {
|
|
55
|
+
return "Use @image1 as the ONLY reference. The face must remain 100% identical to this image.";
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const refs = Array.from({ length: count }, (_, i) => {
|
|
59
|
+
const num = i + 1;
|
|
60
|
+
return `Person ${num}: Use @image${num} - preserve 100% identical facial appearance`;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
return refs.join("\n");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Build face preservation prompt dynamically
|
|
68
|
+
* Supports any number of people (1, 2, 3, N)
|
|
69
|
+
*/
|
|
70
|
+
export function buildFacePreservationPrompt(options: FacePreservationOptions): string {
|
|
71
|
+
const { scenarioPrompt, personCount, customRules, customForbidden } = options;
|
|
72
|
+
|
|
73
|
+
const rules = [...FACE_LOCK_RULES, ...(customRules ?? [])];
|
|
74
|
+
const forbidden = [...FORBIDDEN_ACTIONS, ...(customForbidden ?? [])];
|
|
75
|
+
|
|
76
|
+
const personRefs = buildPersonReferences(personCount);
|
|
77
|
+
const personWord = personCount === 1 ? "person" : "people";
|
|
78
|
+
|
|
79
|
+
return `CRITICAL FACE PRESERVATION: Create a photorealistic image of EXACTLY the ${personCount} ${personWord} from the provided reference photo${personCount > 1 ? "s" : ""}.
|
|
80
|
+
|
|
81
|
+
REFERENCE IMAGES:
|
|
82
|
+
${personRefs}
|
|
83
|
+
|
|
84
|
+
FACE LOCK RULES:
|
|
85
|
+
${rules.map((r) => `- ${r}`).join("\n")}
|
|
86
|
+
|
|
87
|
+
FORBIDDEN:
|
|
88
|
+
${forbidden.map((f) => `- ${f}`).join("\n")}
|
|
89
|
+
|
|
90
|
+
SCENARIO:
|
|
91
|
+
${scenarioPrompt}
|
|
92
|
+
|
|
93
|
+
OUTPUT REQUIREMENTS:
|
|
94
|
+
- Photorealistic photograph quality (professional DSLR)
|
|
95
|
+
- Natural expressions and poses
|
|
96
|
+
- High resolution, detailed textures
|
|
97
|
+
- Each person's face must be 100% identical to their reference photo
|
|
98
|
+
|
|
99
|
+
FINAL VERIFICATION: Before output, confirm all faces match reference images exactly.`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Build a minimal face preservation prompt (for API character limits)
|
|
104
|
+
*/
|
|
105
|
+
export function buildMinimalFacePreservationPrompt(options: FacePreservationOptions): string {
|
|
106
|
+
const { scenarioPrompt, personCount } = options;
|
|
107
|
+
const personRefs = buildPersonReferences(personCount);
|
|
108
|
+
|
|
109
|
+
return `FACE IDENTITY LOCK: ${personCount} person(s) from reference photos.
|
|
110
|
+
${personRefs}
|
|
111
|
+
RULES: Preserve 100% facial identity, no modifications.
|
|
112
|
+
SCENARIO: ${scenarioPrompt}
|
|
113
|
+
OUTPUT: Photorealistic, faces identical to references.`;
|
|
114
|
+
}
|