@umituz/react-native-ai-generation-content 1.20.37 → 1.20.39
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/prompts/domain/entities/BasePromptStructure.ts +195 -0
- package/src/domains/prompts/index.ts +13 -0
- package/src/features/hd-touch-up/presentation/hooks/useHDTouchUpFeature.ts +1 -1
- package/src/features/photo-restoration/presentation/hooks/usePhotoRestoreFeature.ts +1 -1
- package/src/features/remove-background/presentation/hooks/useRemoveBackgroundFeature.ts +1 -1
- package/src/features/upscaling/presentation/hooks/useUpscaleFeature.ts +1 -1
- package/src/presentation/components/AIGenerationConfig.tsx +0 -3
- package/src/presentation/components/flows/AIGenerateWizardFlow.tsx +2 -2
- package/src/presentation/components/flows/AIGenerateWizardFlow.types.ts +15 -2
- package/src/presentation/components/flows/useAIGenerateWizardFlow.ts +13 -6
- package/src/presentation/hooks/generation/orchestrator.ts +4 -1
- package/src/presentation/hooks/generation/useAIFeatureGeneration.ts +16 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.20.
|
|
3
|
+
"version": "1.20.39",
|
|
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",
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Prompt Structure for AI Image Generation
|
|
3
|
+
* Optimized for character identity preservation and photorealistic output
|
|
4
|
+
*
|
|
5
|
+
* This module provides core prompt building blocks that ensure:
|
|
6
|
+
* - Strict facial identity preservation
|
|
7
|
+
* - High-quality photorealistic rendering
|
|
8
|
+
* - Consistent character appearance across generations
|
|
9
|
+
* - Professional photography standards
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Core identity preservation instruction
|
|
14
|
+
* This is the foundation for maintaining facial consistency
|
|
15
|
+
*/
|
|
16
|
+
export const IDENTITY_PRESERVATION_CORE = `CRITICAL IDENTITY PRESERVATION:
|
|
17
|
+
{
|
|
18
|
+
"policy": "MAINTAIN EXACT FACIAL IDENTITY FROM INPUT",
|
|
19
|
+
"rule_1": "Perfect facial identity preservation - the output MUST depict the EXACT SAME PERSON from the input photo.",
|
|
20
|
+
"rule_2": "Preserve all unique facial features: bone structure, eye shape, nose, mouth, facial proportions.",
|
|
21
|
+
"rule_3": "Maintain natural skin texture with pores and realistic details.",
|
|
22
|
+
"rule_4": "Keep the person's recognizable identity while adapting to the scenario context.",
|
|
23
|
+
"rule_5": "NEVER alter core facial characteristics that define the person's identity."
|
|
24
|
+
}`;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Photorealistic rendering instruction
|
|
28
|
+
* Ensures high-quality, professional photography output
|
|
29
|
+
*/
|
|
30
|
+
export const PHOTOREALISTIC_RENDERING = `PHOTOREALISTIC RENDERING REQUIREMENTS:
|
|
31
|
+
{
|
|
32
|
+
"style": "HIGH-END PHOTOREALISTIC PHOTOGRAPH",
|
|
33
|
+
"quality": "8k resolution, ultra-detailed textures, professional photography",
|
|
34
|
+
"lighting": "Cinematic lighting with natural shadows and highlights",
|
|
35
|
+
"camera": "Shot on professional DSLR camera (Canon EOS R5, Sony A7R V, or equivalent)",
|
|
36
|
+
"lens": "Professional lens with appropriate focal length (35mm, 50mm, 85mm)",
|
|
37
|
+
"prohibited": "STRICTLY NO anime, cartoons, illustrations, sketches, 3D renders, or non-photorealistic styles",
|
|
38
|
+
"output": "Must look like a real photograph taken by a professional photographer"
|
|
39
|
+
}`;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Character pose and framing guidelines
|
|
43
|
+
* Prevents absurd poses and ensures natural positioning
|
|
44
|
+
*/
|
|
45
|
+
export const NATURAL_POSE_GUIDELINES = `NATURAL POSE AND FRAMING:
|
|
46
|
+
{
|
|
47
|
+
"framing": "Medium-shot cinematic portrait (waist-up or full-body as appropriate)",
|
|
48
|
+
"pose": "Natural, relaxed pose - standing, sitting, or contextually appropriate position",
|
|
49
|
+
"eye_contact": "Looking directly at the camera with natural expression",
|
|
50
|
+
"body_language": "Confident, natural body language without exaggerated or awkward positions",
|
|
51
|
+
"interaction": "If multiple people: standing side-by-side, close together, or naturally interacting",
|
|
52
|
+
"avoid": "NO absurd poses, unnatural contortions, or physically impossible positions"
|
|
53
|
+
}`;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Complete base prompt combining all core elements
|
|
57
|
+
*/
|
|
58
|
+
export const MASTER_BASE_PROMPT = `${IDENTITY_PRESERVATION_CORE}
|
|
59
|
+
|
|
60
|
+
${PHOTOREALISTIC_RENDERING}
|
|
61
|
+
|
|
62
|
+
${NATURAL_POSE_GUIDELINES}`;
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Creates a complete AI prompt with identity preservation
|
|
66
|
+
*
|
|
67
|
+
* @param scenarioPrompt - The specific scenario description
|
|
68
|
+
* @param options - Optional customization
|
|
69
|
+
* @returns Complete AI-ready prompt
|
|
70
|
+
*/
|
|
71
|
+
export interface CreatePromptOptions {
|
|
72
|
+
/** Include identity preservation instructions (default: true) */
|
|
73
|
+
includeIdentityPreservation?: boolean;
|
|
74
|
+
/** Include photorealistic rendering instructions (default: true) */
|
|
75
|
+
includePhotoRealism?: boolean;
|
|
76
|
+
/** Include natural pose guidelines (default: true) */
|
|
77
|
+
includePoseGuidelines?: boolean;
|
|
78
|
+
/** Additional custom instructions */
|
|
79
|
+
customInstructions?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const createEnhancedPrompt = (
|
|
83
|
+
scenarioPrompt: string,
|
|
84
|
+
options: CreatePromptOptions = {}
|
|
85
|
+
): string => {
|
|
86
|
+
const {
|
|
87
|
+
includeIdentityPreservation = true,
|
|
88
|
+
includePhotoRealism = true,
|
|
89
|
+
includePoseGuidelines = true,
|
|
90
|
+
customInstructions,
|
|
91
|
+
} = options;
|
|
92
|
+
|
|
93
|
+
const parts: string[] = [];
|
|
94
|
+
|
|
95
|
+
if (includeIdentityPreservation) {
|
|
96
|
+
parts.push(IDENTITY_PRESERVATION_CORE);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (includePhotoRealism) {
|
|
100
|
+
parts.push(PHOTOREALISTIC_RENDERING);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (includePoseGuidelines) {
|
|
104
|
+
parts.push(NATURAL_POSE_GUIDELINES);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (customInstructions) {
|
|
108
|
+
parts.push(customInstructions);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
parts.push(`\nSCENARIO DESCRIPTION:\n${scenarioPrompt}`);
|
|
112
|
+
|
|
113
|
+
return parts.join('\n\n');
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Creates a transformation prompt for costume/style changes
|
|
118
|
+
* Maintains identity while changing appearance
|
|
119
|
+
*
|
|
120
|
+
* @param styleName - Name of the target style/theme
|
|
121
|
+
* @param costume - Costume/clothing description
|
|
122
|
+
* @param background - Background/environment description
|
|
123
|
+
* @returns Complete transformation prompt
|
|
124
|
+
*/
|
|
125
|
+
export const createTransformationPrompt = (
|
|
126
|
+
styleName: string,
|
|
127
|
+
costume: string,
|
|
128
|
+
background: string,
|
|
129
|
+
): string => `
|
|
130
|
+
${IDENTITY_PRESERVATION_CORE}
|
|
131
|
+
|
|
132
|
+
${PHOTOREALISTIC_RENDERING}
|
|
133
|
+
|
|
134
|
+
TRANSFORMATION REQUEST:
|
|
135
|
+
{
|
|
136
|
+
"target_theme": "${styleName}",
|
|
137
|
+
"modifications": {
|
|
138
|
+
"clothing_update": "${costume.replace(/\n/g, ' ').trim()}",
|
|
139
|
+
"environment_update": "${background.replace(/\n/g, ' ').trim()}"
|
|
140
|
+
},
|
|
141
|
+
"visual_constraints": {
|
|
142
|
+
"style_matching": "Render as a premium DSLR photograph",
|
|
143
|
+
"face_preservation": "Maintain 100% identity of the person",
|
|
144
|
+
"lighting": "Realistic professional studio or outdoor cinematic lighting",
|
|
145
|
+
"pose": "Natural, contextually appropriate pose"
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
FINAL COMMAND: Transform the input person into a strictly photorealistic ${styleName}. The result MUST be a real-life looking person in high-quality ${styleName} attire, maintaining perfect facial identity.`;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Simplified prompt for scenarios that already include detailed instructions
|
|
153
|
+
* Adds only the essential identity preservation layer
|
|
154
|
+
*
|
|
155
|
+
* @param existingPrompt - The existing detailed prompt
|
|
156
|
+
* @returns Enhanced prompt with identity preservation
|
|
157
|
+
*/
|
|
158
|
+
export const enhanceExistingPrompt = (existingPrompt: string): string => {
|
|
159
|
+
return `${IDENTITY_PRESERVATION_CORE}
|
|
160
|
+
|
|
161
|
+
${existingPrompt}`;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Couple/duo specific prompt enhancement
|
|
166
|
+
* Ensures both people maintain their identities
|
|
167
|
+
*/
|
|
168
|
+
export const COUPLE_IDENTITY_PRESERVATION = `COUPLE IDENTITY PRESERVATION:
|
|
169
|
+
{
|
|
170
|
+
"requirement": "Both individuals must maintain their exact facial identities",
|
|
171
|
+
"person_a": "Perfect preservation of first person's facial features and identity",
|
|
172
|
+
"person_b": "Perfect preservation of second person's facial features and identity",
|
|
173
|
+
"interaction": "Natural, loving interaction between the two people",
|
|
174
|
+
"positioning": "Standing side-by-side, close together, or in contextually appropriate positions",
|
|
175
|
+
"eye_contact": "Both looking at the camera with natural expressions"
|
|
176
|
+
}`;
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Creates a couple-specific prompt
|
|
180
|
+
*
|
|
181
|
+
* @param scenarioPrompt - The scenario description for the couple
|
|
182
|
+
* @returns Complete couple-optimized prompt
|
|
183
|
+
*/
|
|
184
|
+
export const createCouplePrompt = (scenarioPrompt: string): string => {
|
|
185
|
+
return `${IDENTITY_PRESERVATION_CORE}
|
|
186
|
+
|
|
187
|
+
${COUPLE_IDENTITY_PRESERVATION}
|
|
188
|
+
|
|
189
|
+
${PHOTOREALISTIC_RENDERING}
|
|
190
|
+
|
|
191
|
+
${NATURAL_POSE_GUIDELINES}
|
|
192
|
+
|
|
193
|
+
SCENARIO DESCRIPTION:
|
|
194
|
+
${scenarioPrompt}`;
|
|
195
|
+
};
|
|
@@ -91,3 +91,16 @@ export type { ImagePromptResult, ImagePromptBuilderOptions, AnimeSelfiePromptRes
|
|
|
91
91
|
|
|
92
92
|
export { DEFAULT_TEXT_TO_IMAGE_PROMPTS, DEFAULT_TEXT_TO_VOICE_PROMPTS } from './domain/entities/sample-prompts';
|
|
93
93
|
export type { PromptSuggestion } from './domain/entities/sample-prompts';
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
IDENTITY_PRESERVATION_CORE,
|
|
97
|
+
PHOTOREALISTIC_RENDERING,
|
|
98
|
+
NATURAL_POSE_GUIDELINES,
|
|
99
|
+
MASTER_BASE_PROMPT,
|
|
100
|
+
COUPLE_IDENTITY_PRESERVATION,
|
|
101
|
+
createEnhancedPrompt,
|
|
102
|
+
createTransformationPrompt,
|
|
103
|
+
enhanceExistingPrompt,
|
|
104
|
+
createCouplePrompt
|
|
105
|
+
} from './domain/entities/BasePromptStructure';
|
|
106
|
+
export type { CreatePromptOptions } from './domain/entities/BasePromptStructure';
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useSingleImageFeature, type BaseSingleImageHookReturn } from "../../../image-to-image";
|
|
7
|
-
import type { HDTouchUpFeatureConfig
|
|
7
|
+
import type { HDTouchUpFeatureConfig } from "../../domain/types";
|
|
8
8
|
|
|
9
9
|
export interface UseHDTouchUpFeatureProps {
|
|
10
10
|
config: HDTouchUpFeatureConfig;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useSingleImageFeature, type BaseSingleImageHookReturn } from "../../../image-to-image";
|
|
7
|
-
import type { PhotoRestoreFeatureConfig
|
|
7
|
+
import type { PhotoRestoreFeatureConfig } from "../../domain/types";
|
|
8
8
|
|
|
9
9
|
export interface UsePhotoRestoreFeatureProps {
|
|
10
10
|
config: PhotoRestoreFeatureConfig;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useSingleImageFeature, type BaseSingleImageHookReturn } from "../../../image-to-image";
|
|
7
|
-
import type { RemoveBackgroundFeatureConfig
|
|
7
|
+
import type { RemoveBackgroundFeatureConfig } from "../../domain/types";
|
|
8
8
|
|
|
9
9
|
export interface UseRemoveBackgroundFeatureProps {
|
|
10
10
|
config: RemoveBackgroundFeatureConfig;
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { useSingleImageFeature, type BaseSingleImageHookReturn } from "../../../image-to-image";
|
|
7
|
-
import type { UpscaleFeatureConfig
|
|
7
|
+
import type { UpscaleFeatureConfig } from "../../domain/types";
|
|
8
8
|
|
|
9
9
|
export interface UseUpscaleFeatureProps {
|
|
10
10
|
config: UpscaleFeatureConfig;
|
|
@@ -4,7 +4,6 @@ import { View, Image, StyleSheet } from "react-native";
|
|
|
4
4
|
import { AIGenerationHero } from "./AIGenerationHero";
|
|
5
5
|
import { AIGenerationForm } from "./AIGenerationForm";
|
|
6
6
|
import type { AIGenerationFormProps } from "./AIGenerationForm.types";
|
|
7
|
-
import { useAppDesignTokens } from "@umituz/react-native-design-system";
|
|
8
7
|
|
|
9
8
|
export interface AIGenerationConfigProps extends Omit<AIGenerationFormProps, "isGenerating" | "progress"> {
|
|
10
9
|
readonly heroTitle: string;
|
|
@@ -24,8 +23,6 @@ export const AIGenerationConfig: React.FC<AIGenerationConfigProps> = ({
|
|
|
24
23
|
progress = 0,
|
|
25
24
|
...formProps
|
|
26
25
|
}) => {
|
|
27
|
-
const tokens = useAppDesignTokens();
|
|
28
|
-
|
|
29
26
|
return (
|
|
30
27
|
<View style={styles.container}>
|
|
31
28
|
{heroTitle && (
|
|
@@ -61,7 +61,7 @@ export const AIGenerateWizardFlow: React.FC<AIGenerateWizardFlowProps> = ({
|
|
|
61
61
|
<PartnerStepScreen
|
|
62
62
|
t={t}
|
|
63
63
|
onBack={handleBack}
|
|
64
|
-
onContinue={(img:
|
|
64
|
+
onContinue={(img: { uri: string; previewUrl?: string }) => {
|
|
65
65
|
setStepImage(isStep2 ? 1 : 0, { uri: img.uri, previewUrl: img.previewUrl || img.uri });
|
|
66
66
|
handleNext();
|
|
67
67
|
}}
|
|
@@ -184,7 +184,7 @@ export const AIGenerateWizardFlow: React.FC<AIGenerateWizardFlowProps> = ({
|
|
|
184
184
|
isGenerating={isGenerating}
|
|
185
185
|
progress={progress}
|
|
186
186
|
presets={presets}
|
|
187
|
-
onPresetPress={handleGenerate
|
|
187
|
+
onPresetPress={() => { void handleGenerate(); }}
|
|
188
188
|
prompt={prompt}
|
|
189
189
|
onPromptChange={setPrompt}
|
|
190
190
|
styles={styleOptions}
|
|
@@ -33,11 +33,24 @@ export interface AIGenerateWizardTranslations {
|
|
|
33
33
|
readonly maxFileSize: string;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export interface StyleOption {
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly label: string;
|
|
39
|
+
readonly icon?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface PresetOption {
|
|
43
|
+
readonly id: string;
|
|
44
|
+
readonly label: string;
|
|
45
|
+
readonly prompt?: string;
|
|
46
|
+
readonly icon?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
36
49
|
export interface AIGenerateWizardFlowProps {
|
|
37
50
|
readonly featureType: string;
|
|
38
51
|
readonly translations: AIGenerateWizardTranslations;
|
|
39
|
-
readonly styleOptions:
|
|
40
|
-
readonly presets:
|
|
52
|
+
readonly styleOptions: StyleOption[];
|
|
53
|
+
readonly presets: PresetOption[];
|
|
41
54
|
readonly durationOptions: number[];
|
|
42
55
|
readonly onGenerate: (data: {
|
|
43
56
|
prompt: string;
|
|
@@ -3,9 +3,16 @@ import { useMemo, useCallback, useEffect } from "react";
|
|
|
3
3
|
import { useAIGenerateState, AIGenerateStep } from "../../hooks/generation/useAIGenerateState";
|
|
4
4
|
import { getAIFeatureConfig, hasAIFeature } from "../../screens/ai-feature/registry";
|
|
5
5
|
|
|
6
|
+
interface GenerationData {
|
|
7
|
+
prompt: string;
|
|
8
|
+
style: string;
|
|
9
|
+
duration: number;
|
|
10
|
+
images: { uri: string; previewUrl?: string }[];
|
|
11
|
+
}
|
|
12
|
+
|
|
6
13
|
interface UseAIGenerateWizardFlowProps {
|
|
7
14
|
featureType: string;
|
|
8
|
-
onGenerate: (data:
|
|
15
|
+
onGenerate: (data: GenerationData) => Promise<string | null | void>;
|
|
9
16
|
onBack?: () => void;
|
|
10
17
|
}
|
|
11
18
|
|
|
@@ -15,15 +22,15 @@ export function useAIGenerateWizardFlow({
|
|
|
15
22
|
onBack: onBackProp,
|
|
16
23
|
}: UseAIGenerateWizardFlowProps) {
|
|
17
24
|
const state = useAIGenerateState();
|
|
18
|
-
const {
|
|
19
|
-
currentStep, setCurrentStep,
|
|
20
|
-
setProgress, setResult, images, prompt, selectedStyle,
|
|
21
|
-
selectedDuration
|
|
25
|
+
const {
|
|
26
|
+
currentStep, setCurrentStep, setIsGenerating,
|
|
27
|
+
setProgress, setResult, images, prompt, selectedStyle,
|
|
28
|
+
selectedDuration
|
|
22
29
|
} = state;
|
|
23
30
|
|
|
24
31
|
const imageCountRequired = useMemo(() => {
|
|
25
32
|
if (!featureType || !hasAIFeature(featureType)) return 0;
|
|
26
|
-
const config = getAIFeatureConfig(featureType as
|
|
33
|
+
const config = getAIFeatureConfig(featureType as Parameters<typeof getAIFeatureConfig>[0]);
|
|
27
34
|
if (config.mode === "dual" || config.mode === "dual-video") return 2;
|
|
28
35
|
if (config.mode === "single" || config.mode === "single-with-prompt")
|
|
29
36
|
return 1;
|
|
@@ -70,7 +70,10 @@ export const useGenerationOrchestrator = <TInput, TResult>(
|
|
|
70
70
|
|
|
71
71
|
const offlineStore = useOfflineStore();
|
|
72
72
|
const { showError, showSuccess } = useAlert();
|
|
73
|
-
const defaultCredits = useDeductCredit({ userId, onCreditsExhausted }) as
|
|
73
|
+
const defaultCredits = useDeductCredit({ userId, onCreditsExhausted }) as {
|
|
74
|
+
checkCredits: (amount: number) => Promise<boolean>;
|
|
75
|
+
deductCredit: (amount: number) => Promise<void>;
|
|
76
|
+
};
|
|
74
77
|
|
|
75
78
|
// Use provided credit callbacks or default to useDeductCredit hook
|
|
76
79
|
const checkCredits = credits?.checkCredits ?? defaultCredits.checkCredits;
|
|
@@ -9,12 +9,25 @@ import { prepareImage } from "../../../infrastructure/utils";
|
|
|
9
9
|
import type { AIFeatureId } from "../../screens/ai-feature/types";
|
|
10
10
|
import type { ImageFeatureType, VideoFeatureType } from "../../../domain/interfaces";
|
|
11
11
|
|
|
12
|
+
interface AlertMessages {
|
|
13
|
+
success?: string;
|
|
14
|
+
error?: string;
|
|
15
|
+
network?: string;
|
|
16
|
+
credits?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface GenerationError {
|
|
20
|
+
type: string;
|
|
21
|
+
message: string;
|
|
22
|
+
originalError?: Error;
|
|
23
|
+
}
|
|
24
|
+
|
|
12
25
|
interface FeatureGenerationConfig {
|
|
13
26
|
featureType: AIFeatureId;
|
|
14
27
|
userId?: string;
|
|
15
|
-
alertMessages:
|
|
16
|
-
onSuccess?: (result:
|
|
17
|
-
onError?: (error:
|
|
28
|
+
alertMessages: AlertMessages;
|
|
29
|
+
onSuccess?: (result: string) => void;
|
|
30
|
+
onError?: (error: GenerationError) => void;
|
|
18
31
|
creditCost?: number;
|
|
19
32
|
onCreditsExhausted?: () => void;
|
|
20
33
|
}
|