@umituz/react-native-ai-generation-content 1.61.64 → 1.62.0
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/face-detection/README.md +19 -304
- package/src/domains/face-detection/domain/types/face-preservation.types.ts +50 -0
- package/src/domains/face-detection/index.ts +40 -2
- package/src/domains/face-detection/infrastructure/builders/facePreservationPromptBuilder.ts +66 -0
- package/src/domains/face-detection/infrastructure/integration/imageGenerationIntegration.ts +85 -0
- package/src/domains/image-to-video/domain/constants/index.ts +0 -5
- package/src/domains/image-to-video/domain/types/index.ts +0 -3
- package/src/domains/image-to-video/index.ts +0 -7
- package/src/domains/image-to-video/presentation/components/index.ts +0 -4
- package/src/exports/features.ts +3 -4
- package/src/domains/image-to-video/domain/constants/music.constants.ts +0 -53
- package/src/domains/image-to-video/domain/types/music.types.ts +0 -21
- package/src/domains/image-to-video/presentation/components/MusicMoodSelector.tsx +0 -181
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-ai-generation-content",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.62.0",
|
|
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",
|
|
@@ -1,314 +1,29 @@
|
|
|
1
|
-
# Face Detection
|
|
1
|
+
# Face Detection & Preservation
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
AI-powered face detection and preservation for image generation tasks.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Features
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
cropFace,
|
|
13
|
-
checkFaceQuality,
|
|
14
|
-
findMatchingFace
|
|
15
|
-
} from '@umituz/react-native-ai-generation-content';
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
**Location**: `src/domains/face-detection/`
|
|
19
|
-
|
|
20
|
-
## 🎯 Domain Purpose
|
|
21
|
-
|
|
22
|
-
Comprehensive face detection and analysis capabilities for AI features. Detect faces in images, analyze facial features and attributes, support multiple faces, extract facial measurements and landmarks, and enable face matching for various AI generation features.
|
|
23
|
-
|
|
24
|
-
---
|
|
25
|
-
|
|
26
|
-
## 📋 Usage Strategy
|
|
27
|
-
|
|
28
|
-
### When to Use This Domain
|
|
29
|
-
|
|
30
|
-
✅ **Use Cases:**
|
|
31
|
-
- Preparing images for face swap
|
|
32
|
-
- Validating photos for AI features
|
|
33
|
-
- Detecting multiple faces in group photos
|
|
34
|
-
- Quality checking before processing
|
|
35
|
-
- Face matching and verification
|
|
36
|
-
|
|
37
|
-
❌ **When NOT to Use:**
|
|
38
|
-
- Real-time video processing (use specialized tools)
|
|
39
|
-
- Surveillance or tracking (ethical concerns)
|
|
40
|
-
- Biometric authentication (use security libraries)
|
|
41
|
-
- Age verification for legal purposes
|
|
42
|
-
|
|
43
|
-
### Implementation Strategy
|
|
44
|
-
|
|
45
|
-
1. **Detect faces** before AI generation
|
|
46
|
-
2. **Validate quality** of detected faces
|
|
47
|
-
3. **Handle multiple faces** appropriately
|
|
48
|
-
4. **Extract landmarks** for processing
|
|
49
|
-
5. **Compare faces** when needed
|
|
50
|
-
6. **Crop faces** for focused processing
|
|
51
|
-
7. **Provide feedback** on detection results
|
|
52
|
-
|
|
53
|
-
---
|
|
54
|
-
|
|
55
|
-
## ⚠️ Critical Rules (MUST FOLLOW)
|
|
56
|
-
|
|
57
|
-
### 1. Detection Requirements
|
|
58
|
-
- **MUST** detect faces before processing
|
|
59
|
-
- **MUST** handle no-face scenarios
|
|
60
|
-
- **MUST** support multiple faces
|
|
61
|
-
- **MUST** return confidence scores
|
|
62
|
-
- **MUST** provide bounding boxes
|
|
63
|
-
|
|
64
|
-
### 2. Quality Validation
|
|
65
|
-
- **MUST** check face quality before use
|
|
66
|
-
- **MUST** provide quality feedback
|
|
67
|
-
- **MUST** handle poor quality gracefully
|
|
68
|
-
- **MUST** guide users to better photos
|
|
69
|
-
- **MUST** validate clarity and visibility
|
|
70
|
-
|
|
71
|
-
### 3. Multiple Face Handling
|
|
72
|
-
- **MUST** detect all faces in image
|
|
73
|
-
- **MUST** allow face selection
|
|
74
|
-
- **MUST** provide face indexing
|
|
75
|
-
- **MUST** handle face ordering
|
|
76
|
-
- **MUST** support group photos
|
|
77
|
-
|
|
78
|
-
### 4. Performance
|
|
79
|
-
- **MUST** optimize detection speed
|
|
80
|
-
- **MUST** cache detection results
|
|
81
|
-
- **MUST** handle large images
|
|
82
|
-
- **MUST NOT** block main thread
|
|
83
|
-
- **MUST** implement efficient algorithms
|
|
84
|
-
|
|
85
|
-
### 5. User Experience
|
|
86
|
-
- **MUST** provide clear feedback
|
|
87
|
-
- **MUST** explain detection failures
|
|
88
|
-
- **MUST** guide users to better photos
|
|
89
|
-
- **MUST** show detected faces
|
|
90
|
-
- **MUST** allow face selection
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
## 🚫 Prohibitions (MUST AVOID)
|
|
95
|
-
|
|
96
|
-
### Strictly Forbidden
|
|
97
|
-
|
|
98
|
-
❌ **NEVER** do the following:
|
|
99
|
-
|
|
100
|
-
1. **No Skipping Detection**
|
|
101
|
-
- Always detect before processing
|
|
102
|
-
- Never assume face presence
|
|
103
|
-
- Validate detection results
|
|
104
|
-
|
|
105
|
-
2. **No Poor Quality Processing**
|
|
106
|
-
- Always check quality first
|
|
107
|
-
- Never process poor quality faces
|
|
108
|
-
- Guide users to better photos
|
|
109
|
-
|
|
110
|
-
3. **No Missing Feedback**
|
|
111
|
-
- Always explain detection results
|
|
112
|
-
- Never silently fail
|
|
113
|
-
- Provide actionable guidance
|
|
114
|
-
|
|
115
|
-
4. **No Privacy Violations**
|
|
116
|
-
- Never store faces without consent
|
|
117
|
-
- Always handle data properly
|
|
118
|
-
- Comply with regulations
|
|
119
|
-
|
|
120
|
-
5. **No Biased Detection**
|
|
121
|
-
- Ensure fair detection across demographics
|
|
122
|
-
- Test for bias regularly
|
|
123
|
-
- Use diverse training data
|
|
124
|
-
|
|
125
|
-
6. **No Missing Context**
|
|
126
|
-
- Always explain what was detected
|
|
127
|
-
- Show face locations
|
|
128
|
-
- Provide confidence scores
|
|
129
|
-
|
|
130
|
-
7. **No Blocking UI**
|
|
131
|
-
- Never block on detection
|
|
132
|
-
- Show progress indicators
|
|
133
|
-
- Allow cancellation
|
|
134
|
-
|
|
135
|
-
---
|
|
136
|
-
|
|
137
|
-
## 🤖 AI Agent Directions
|
|
138
|
-
|
|
139
|
-
### For AI Code Generation Tools
|
|
7
|
+
- ✅ **AI-based Face Detection** - Detect faces in images using AI vision models
|
|
8
|
+
- ✅ **Face Preservation Modes** - Multiple preservation strategies (strict, balanced, minimal)
|
|
9
|
+
- ✅ **Provider-Agnostic** - Works with any AI vision provider
|
|
10
|
+
- ✅ **Image Generation Integration** - Easy integration with image-to-video, image-to-image, etc.
|
|
11
|
+
- ✅ **React Hooks** - Ready-to-use hooks for React Native
|
|
140
12
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
```
|
|
144
|
-
You are implementing face detection using @umituz/react-native-ai-generation-content.
|
|
145
|
-
|
|
146
|
-
REQUIREMENTS:
|
|
147
|
-
1. Import face detection functions
|
|
148
|
-
2. Detect faces before AI generation
|
|
149
|
-
3. Validate face quality
|
|
150
|
-
4. Handle multiple faces
|
|
151
|
-
5. Extract landmarks for processing
|
|
152
|
-
6. Provide clear user feedback
|
|
153
|
-
7. Handle no-face scenarios
|
|
154
|
-
8. Optimize for performance
|
|
155
|
-
|
|
156
|
-
CRITICAL RULES:
|
|
157
|
-
- MUST detect before processing
|
|
158
|
-
- MUST validate face quality
|
|
159
|
-
- MUST handle multiple faces
|
|
160
|
-
- MUST provide clear feedback
|
|
161
|
-
- MUST guide users to better photos
|
|
162
|
-
- MUST handle no-face scenarios
|
|
163
|
-
|
|
164
|
-
DETECTION FUNCTIONS:
|
|
165
|
-
- detectFaces: Detect all faces in image
|
|
166
|
-
- analyzeFace: Analyze facial features
|
|
167
|
-
- compareFaces: Compare two faces
|
|
168
|
-
- cropFace: Crop face from image
|
|
169
|
-
- checkFaceQuality: Validate face quality
|
|
170
|
-
- findMatchingFace: Find face in group photo
|
|
171
|
-
|
|
172
|
-
FACE DATA:
|
|
173
|
-
- boundingBox: Face location and size
|
|
174
|
-
- confidence: Detection confidence (0-1)
|
|
175
|
-
- landmarks: Facial feature points
|
|
176
|
-
- gender: Detected gender
|
|
177
|
-
- age: Estimated age range
|
|
178
|
-
- emotions: Emotion probabilities
|
|
179
|
-
|
|
180
|
-
QUALITY METRICS:
|
|
181
|
-
- brightness: Image brightness
|
|
182
|
-
- sharpness: Face clarity
|
|
183
|
-
- overall: 'good' | 'fair' | 'poor'
|
|
184
|
-
|
|
185
|
-
STRICTLY FORBIDDEN:
|
|
186
|
-
- No skipping detection
|
|
187
|
-
- No poor quality processing
|
|
188
|
-
- No missing feedback
|
|
189
|
-
- No privacy violations
|
|
190
|
-
- No biased detection
|
|
191
|
-
- No missing context
|
|
192
|
-
- No blocking UI
|
|
193
|
-
|
|
194
|
-
QUALITY CHECKLIST:
|
|
195
|
-
- [ ] Face detection implemented
|
|
196
|
-
- [ ] Quality validation added
|
|
197
|
-
- [ ] Multiple faces handled
|
|
198
|
-
- [ ] Clear feedback provided
|
|
199
|
-
- [ ] No-face scenarios handled
|
|
200
|
-
- [ ] Performance optimized
|
|
201
|
-
- [ ] Privacy protected
|
|
202
|
-
- [ ] Bias tested
|
|
203
|
-
- [ ] User guidance provided
|
|
204
|
-
- [ ] Error handling complete
|
|
205
|
-
```
|
|
206
|
-
|
|
207
|
-
---
|
|
208
|
-
|
|
209
|
-
## 🛠️ Configuration Strategy
|
|
210
|
-
|
|
211
|
-
### Detection Result
|
|
13
|
+
## Quick Example
|
|
212
14
|
|
|
213
15
|
```typescript
|
|
214
|
-
|
|
215
|
-
faces: DetectedFace[];
|
|
216
|
-
imageWidth: number;
|
|
217
|
-
imageHeight: number;
|
|
218
|
-
processingTime: number;
|
|
219
|
-
}
|
|
16
|
+
import { prepareImageGenerationWithFacePreservation } from "@umituz/react-native-ai-generation-content/domains/face-detection";
|
|
220
17
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
}
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
### Face Analysis
|
|
18
|
+
// Enhance your generation prompts with face preservation
|
|
19
|
+
const generation = prepareImageGenerationWithFacePreservation({
|
|
20
|
+
prompt: "Transform into cartoon style",
|
|
21
|
+
faceDetectionResult: faceResult,
|
|
22
|
+
preservationMode: "balanced",
|
|
23
|
+
});
|
|
230
24
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
gender: 'male' | 'female' | 'unknown';
|
|
234
|
-
age: {
|
|
235
|
-
min: number;
|
|
236
|
-
max: number;
|
|
237
|
-
estimated: number;
|
|
238
|
-
};
|
|
239
|
-
emotions: {
|
|
240
|
-
happy: number;
|
|
241
|
-
sad: number;
|
|
242
|
-
angry: number;
|
|
243
|
-
surprised: number;
|
|
244
|
-
neutral: number;
|
|
245
|
-
};
|
|
246
|
-
dominantEmotion: string;
|
|
247
|
-
landmarks: FacialLandmarks;
|
|
248
|
-
faceQuality: {
|
|
249
|
-
brightness: number;
|
|
250
|
-
sharpness: number;
|
|
251
|
-
overall: 'good' | 'fair' | 'poor';
|
|
252
|
-
};
|
|
253
|
-
}
|
|
25
|
+
// Use enhanced prompt
|
|
26
|
+
await generateVideo({ prompt: generation.enhancedPrompt });
|
|
254
27
|
```
|
|
255
28
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
## 📊 Core Functions
|
|
259
|
-
|
|
260
|
-
### Detection
|
|
261
|
-
- `detectFaces()` - Find all faces
|
|
262
|
-
- `analyzeFace()` - Analyze single face
|
|
263
|
-
- `checkFaceQuality()` - Validate quality
|
|
264
|
-
|
|
265
|
-
### Processing
|
|
266
|
-
- `cropFace()` - Extract face
|
|
267
|
-
- `findMatchingFace()` - Match in group
|
|
268
|
-
- `compareFaces()` - Compare two faces
|
|
269
|
-
|
|
270
|
-
### Components
|
|
271
|
-
- `FaceDetectionOverlay` - Visual overlay
|
|
272
|
-
- `FaceAnalysisDisplay` - Analysis results
|
|
273
|
-
|
|
274
|
-
---
|
|
275
|
-
|
|
276
|
-
## 🎨 Best Practices
|
|
277
|
-
|
|
278
|
-
### Image Quality
|
|
279
|
-
- Use high-quality, well-lit photos
|
|
280
|
-
- Ensure faces are clearly visible
|
|
281
|
-
- Forward-facing photos work best
|
|
282
|
-
- Avoid extreme angles
|
|
283
|
-
|
|
284
|
-
### User Feedback
|
|
285
|
-
- Explain why detection failed
|
|
286
|
-
- Guide to better photos
|
|
287
|
-
- Show what was detected
|
|
288
|
-
- Provide confidence scores
|
|
289
|
-
|
|
290
|
-
### Performance
|
|
291
|
-
- Cache detection results
|
|
292
|
-
- Optimize image sizes
|
|
293
|
-
- Implement lazy loading
|
|
294
|
-
- Background processing
|
|
295
|
-
|
|
296
|
-
---
|
|
297
|
-
|
|
298
|
-
## 🐛 Common Pitfalls
|
|
299
|
-
|
|
300
|
-
❌ **No face detected**: Guide user to better photo
|
|
301
|
-
❌ **Poor quality**: Check quality first
|
|
302
|
-
❌ **Multiple faces**: Allow face selection
|
|
303
|
-
❌ **Slow detection**: Optimize, cache results
|
|
304
|
-
|
|
305
|
-
---
|
|
306
|
-
|
|
307
|
-
## 📚 Related Features
|
|
308
|
-
|
|
309
|
-
- [Face Swap](../../features/face-swap) - Swap faces between images
|
|
310
|
-
|
|
311
|
-
---
|
|
312
|
-
|
|
313
|
-
**Last Updated**: 2025-01-08
|
|
314
|
-
**Version**: 2.0.0 (Strategy-based Documentation)
|
|
29
|
+
See [index.ts](./index.ts) for complete API.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Face Preservation Types
|
|
3
|
+
* Enhanced face detection with preservation strategies
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export type FacePreservationMode = "strict" | "balanced" | "minimal" | "none";
|
|
7
|
+
|
|
8
|
+
export interface FaceMetadata {
|
|
9
|
+
readonly hasFace: boolean;
|
|
10
|
+
readonly confidence: number;
|
|
11
|
+
readonly position?: {
|
|
12
|
+
readonly x: number;
|
|
13
|
+
readonly y: number;
|
|
14
|
+
readonly width: number;
|
|
15
|
+
readonly height: number;
|
|
16
|
+
};
|
|
17
|
+
readonly features?: {
|
|
18
|
+
readonly eyes: boolean;
|
|
19
|
+
readonly nose: boolean;
|
|
20
|
+
readonly mouth: boolean;
|
|
21
|
+
};
|
|
22
|
+
readonly quality?: "high" | "medium" | "low";
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface FacePreservationConfig {
|
|
26
|
+
readonly mode: FacePreservationMode;
|
|
27
|
+
readonly minConfidence: number;
|
|
28
|
+
readonly requireFullFace?: boolean;
|
|
29
|
+
readonly allowMultipleFaces?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface FacePreservationPrompt {
|
|
33
|
+
readonly basePrompt: string;
|
|
34
|
+
readonly faceGuidance: string;
|
|
35
|
+
readonly preservationWeight: number;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const DEFAULT_PRESERVATION_CONFIG: FacePreservationConfig = {
|
|
39
|
+
mode: "balanced",
|
|
40
|
+
minConfidence: 0.5,
|
|
41
|
+
requireFullFace: false,
|
|
42
|
+
allowMultipleFaces: true,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const PRESERVATION_MODE_WEIGHTS: Record<FacePreservationMode, number> = {
|
|
46
|
+
strict: 1.0,
|
|
47
|
+
balanced: 0.7,
|
|
48
|
+
minimal: 0.4,
|
|
49
|
+
none: 0.0,
|
|
50
|
+
};
|
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* React Native AI Face Detection - Public API
|
|
2
|
+
* React Native AI Face Detection & Preservation - Public API
|
|
3
3
|
*
|
|
4
|
-
* AI-powered face detection for
|
|
4
|
+
* AI-powered face detection and preservation for image generation
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
// Domain - Entities
|
|
7
8
|
export type {
|
|
8
9
|
FaceDetectionResult,
|
|
9
10
|
FaceValidationState,
|
|
10
11
|
FaceDetectionConfig,
|
|
11
12
|
} from "./domain/entities/FaceDetection";
|
|
12
13
|
|
|
14
|
+
// Domain - Face Preservation Types
|
|
15
|
+
export type {
|
|
16
|
+
FacePreservationMode,
|
|
17
|
+
FaceMetadata,
|
|
18
|
+
FacePreservationConfig,
|
|
19
|
+
FacePreservationPrompt,
|
|
20
|
+
} from "./domain/types/face-preservation.types";
|
|
21
|
+
|
|
22
|
+
export {
|
|
23
|
+
DEFAULT_PRESERVATION_CONFIG,
|
|
24
|
+
PRESERVATION_MODE_WEIGHTS,
|
|
25
|
+
} from "./domain/types/face-preservation.types";
|
|
26
|
+
|
|
27
|
+
// Domain - Constants
|
|
13
28
|
export { FACE_DETECTION_CONFIG, FACE_DETECTION_PROMPTS } from "./domain/constants/faceDetectionConstants";
|
|
14
29
|
|
|
30
|
+
// Infrastructure - Validators
|
|
15
31
|
export {
|
|
16
32
|
isValidFace,
|
|
17
33
|
parseDetectionResponse,
|
|
@@ -19,9 +35,31 @@ export {
|
|
|
19
35
|
createSuccessResult,
|
|
20
36
|
} from "./infrastructure/validators/faceValidator";
|
|
21
37
|
|
|
38
|
+
// Infrastructure - Analyzers
|
|
22
39
|
export { analyzeImageForFace } from "./infrastructure/analyzers/faceAnalyzer";
|
|
40
|
+
export type { AIAnalyzerFunction } from "./infrastructure/analyzers/faceAnalyzer";
|
|
41
|
+
|
|
42
|
+
// Infrastructure - Face Preservation Builders
|
|
43
|
+
export {
|
|
44
|
+
buildFacePreservationPrompt,
|
|
45
|
+
combineFacePreservationPrompt,
|
|
46
|
+
getFacePreservationWeight,
|
|
47
|
+
} from "./infrastructure/builders/facePreservationPromptBuilder";
|
|
48
|
+
export type { BuildPreservationPromptOptions } from "./infrastructure/builders/facePreservationPromptBuilder";
|
|
49
|
+
|
|
50
|
+
// Infrastructure - Image Generation Integration
|
|
51
|
+
export {
|
|
52
|
+
prepareImageGenerationWithFacePreservation,
|
|
53
|
+
shouldEnableFacePreservation,
|
|
54
|
+
} from "./infrastructure/integration/imageGenerationIntegration";
|
|
55
|
+
export type {
|
|
56
|
+
ImageGenerationWithFacePreservation,
|
|
57
|
+
PrepareImageGenerationOptions,
|
|
58
|
+
} from "./infrastructure/integration/imageGenerationIntegration";
|
|
23
59
|
|
|
60
|
+
// Presentation - Hooks
|
|
24
61
|
export { useFaceDetection } from "./presentation/hooks/useFaceDetection";
|
|
25
62
|
|
|
63
|
+
// Presentation - Components
|
|
26
64
|
export { FaceValidationStatus } from "./presentation/components/FaceValidationStatus";
|
|
27
65
|
export { FaceDetectionToggle } from "./presentation/components/FaceDetectionToggle";
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Face Preservation Prompt Builder
|
|
3
|
+
* Builds prompts that preserve facial features in image generation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
FacePreservationMode,
|
|
8
|
+
FacePreservationPrompt,
|
|
9
|
+
FaceMetadata,
|
|
10
|
+
} from "../../domain/types/face-preservation.types";
|
|
11
|
+
import { PRESERVATION_MODE_WEIGHTS } from "../../domain/types/face-preservation.types";
|
|
12
|
+
|
|
13
|
+
const PRESERVATION_PROMPTS: Record<FacePreservationMode, string> = {
|
|
14
|
+
strict: "Preserve the exact facial features, expressions, and details from the original image. Maintain face structure, skin tone, eyes, nose, and mouth precisely.",
|
|
15
|
+
balanced: "Keep the main facial features recognizable while allowing natural variations. Preserve overall face structure and key characteristics.",
|
|
16
|
+
minimal: "Maintain basic facial proportions and general appearance while allowing creative interpretation.",
|
|
17
|
+
none: "",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export interface BuildPreservationPromptOptions {
|
|
21
|
+
readonly mode: FacePreservationMode;
|
|
22
|
+
readonly basePrompt: string;
|
|
23
|
+
readonly faceMetadata?: FaceMetadata;
|
|
24
|
+
readonly customGuidance?: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function buildFacePreservationPrompt(
|
|
28
|
+
options: BuildPreservationPromptOptions,
|
|
29
|
+
): FacePreservationPrompt {
|
|
30
|
+
const { mode, basePrompt, faceMetadata, customGuidance } = options;
|
|
31
|
+
|
|
32
|
+
if (mode === "none" || !faceMetadata?.hasFace) {
|
|
33
|
+
return {
|
|
34
|
+
basePrompt,
|
|
35
|
+
faceGuidance: "",
|
|
36
|
+
preservationWeight: 0,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const faceGuidance = customGuidance || PRESERVATION_PROMPTS[mode];
|
|
41
|
+
const preservationWeight = PRESERVATION_MODE_WEIGHTS[mode];
|
|
42
|
+
|
|
43
|
+
const qualityNote = faceMetadata?.quality === "high"
|
|
44
|
+
? " High quality facial details detected."
|
|
45
|
+
: "";
|
|
46
|
+
|
|
47
|
+
const enhancedGuidance = `${faceGuidance}${qualityNote}`;
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
basePrompt,
|
|
51
|
+
faceGuidance: enhancedGuidance,
|
|
52
|
+
preservationWeight,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function combineFacePreservationPrompt(prompt: FacePreservationPrompt): string {
|
|
57
|
+
if (!prompt.faceGuidance) {
|
|
58
|
+
return prompt.basePrompt;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return `${prompt.basePrompt}\n\nFace Preservation: ${prompt.faceGuidance}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function getFacePreservationWeight(mode: FacePreservationMode): number {
|
|
65
|
+
return PRESERVATION_MODE_WEIGHTS[mode];
|
|
66
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Image Generation Integration
|
|
3
|
+
* Utilities for integrating face preservation with image generation flows
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { FaceDetectionResult } from "../../domain/entities/FaceDetection";
|
|
7
|
+
import type {
|
|
8
|
+
FacePreservationMode,
|
|
9
|
+
FaceMetadata,
|
|
10
|
+
FacePreservationConfig,
|
|
11
|
+
} from "../../domain/types/face-preservation.types";
|
|
12
|
+
import {
|
|
13
|
+
buildFacePreservationPrompt,
|
|
14
|
+
combineFacePreservationPrompt,
|
|
15
|
+
} from "../builders/facePreservationPromptBuilder";
|
|
16
|
+
|
|
17
|
+
export interface ImageGenerationWithFacePreservation {
|
|
18
|
+
readonly originalPrompt: string;
|
|
19
|
+
readonly enhancedPrompt: string;
|
|
20
|
+
readonly preservationMode: FacePreservationMode;
|
|
21
|
+
readonly faceDetected: boolean;
|
|
22
|
+
readonly shouldPreserveFace: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface PrepareImageGenerationOptions {
|
|
26
|
+
readonly prompt: string;
|
|
27
|
+
readonly faceDetectionResult?: FaceDetectionResult;
|
|
28
|
+
readonly preservationMode?: FacePreservationMode;
|
|
29
|
+
readonly config?: Partial<FacePreservationConfig>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function prepareImageGenerationWithFacePreservation(
|
|
33
|
+
options: PrepareImageGenerationOptions,
|
|
34
|
+
): ImageGenerationWithFacePreservation {
|
|
35
|
+
const {
|
|
36
|
+
prompt,
|
|
37
|
+
faceDetectionResult,
|
|
38
|
+
preservationMode = "balanced",
|
|
39
|
+
config,
|
|
40
|
+
} = options;
|
|
41
|
+
|
|
42
|
+
const faceDetected = faceDetectionResult?.hasFace ?? false;
|
|
43
|
+
const minConfidence = config?.minConfidence ?? 0.5;
|
|
44
|
+
const meetsConfidence = (faceDetectionResult?.confidence ?? 0) >= minConfidence;
|
|
45
|
+
const shouldPreserveFace = faceDetected && meetsConfidence && preservationMode !== "none";
|
|
46
|
+
|
|
47
|
+
if (!shouldPreserveFace) {
|
|
48
|
+
return {
|
|
49
|
+
originalPrompt: prompt,
|
|
50
|
+
enhancedPrompt: prompt,
|
|
51
|
+
preservationMode: "none",
|
|
52
|
+
faceDetected,
|
|
53
|
+
shouldPreserveFace: false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const faceMetadata: FaceMetadata = {
|
|
58
|
+
hasFace: faceDetected,
|
|
59
|
+
confidence: faceDetectionResult?.confidence ?? 0,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const preservationPrompt = buildFacePreservationPrompt({
|
|
63
|
+
mode: preservationMode,
|
|
64
|
+
basePrompt: prompt,
|
|
65
|
+
faceMetadata,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const enhancedPrompt = combineFacePreservationPrompt(preservationPrompt);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
originalPrompt: prompt,
|
|
72
|
+
enhancedPrompt,
|
|
73
|
+
preservationMode,
|
|
74
|
+
faceDetected,
|
|
75
|
+
shouldPreserveFace: true,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function shouldEnableFacePreservation(
|
|
80
|
+
faceDetectionResult?: FaceDetectionResult,
|
|
81
|
+
minConfidence: number = 0.5,
|
|
82
|
+
): boolean {
|
|
83
|
+
if (!faceDetectionResult) return false;
|
|
84
|
+
return faceDetectionResult.hasFace && faceDetectionResult.confidence >= minConfidence;
|
|
85
|
+
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
// Animation Types
|
|
2
2
|
export type { AnimationStyle, AnimationStyleId } from "./animation.types";
|
|
3
3
|
|
|
4
|
-
// Music Types
|
|
5
|
-
export type { MusicMood, MusicMoodId } from "./music.types";
|
|
6
|
-
|
|
7
4
|
// Duration Types
|
|
8
5
|
export type { VideoDuration, DurationOption } from "./duration.types";
|
|
9
6
|
|
|
@@ -10,9 +10,6 @@
|
|
|
10
10
|
// Animation Types
|
|
11
11
|
export type { AnimationStyle, AnimationStyleId } from "./domain";
|
|
12
12
|
|
|
13
|
-
// Music Types
|
|
14
|
-
export type { MusicMood, MusicMoodId } from "./domain";
|
|
15
|
-
|
|
16
13
|
// Duration Types
|
|
17
14
|
export type { VideoDuration, DurationOption } from "./domain";
|
|
18
15
|
|
|
@@ -54,8 +51,6 @@ export type {
|
|
|
54
51
|
export {
|
|
55
52
|
DEFAULT_ANIMATION_STYLES as IMAGE_TO_VIDEO_ANIMATION_STYLES,
|
|
56
53
|
DEFAULT_ANIMATION_STYLE_ID as IMAGE_TO_VIDEO_DEFAULT_ANIMATION,
|
|
57
|
-
DEFAULT_MUSIC_MOODS as IMAGE_TO_VIDEO_MUSIC_MOODS,
|
|
58
|
-
DEFAULT_MUSIC_MOOD_ID as IMAGE_TO_VIDEO_DEFAULT_MUSIC,
|
|
59
54
|
DEFAULT_DURATION_OPTIONS as IMAGE_TO_VIDEO_DURATION_OPTIONS,
|
|
60
55
|
DEFAULT_VIDEO_DURATION as IMAGE_TO_VIDEO_DEFAULT_DURATION,
|
|
61
56
|
DEFAULT_FORM_VALUES as IMAGE_TO_VIDEO_FORM_DEFAULTS,
|
|
@@ -101,7 +96,6 @@ export type {
|
|
|
101
96
|
export {
|
|
102
97
|
ImageToVideoAnimationStyleSelector,
|
|
103
98
|
ImageToVideoDurationSelector,
|
|
104
|
-
ImageToVideoMusicMoodSelector,
|
|
105
99
|
ImageToVideoSelectionGrid,
|
|
106
100
|
ImageToVideoGenerateButton,
|
|
107
101
|
} from "./presentation";
|
|
@@ -109,7 +103,6 @@ export {
|
|
|
109
103
|
export type {
|
|
110
104
|
ImageToVideoAnimationStyleSelectorProps,
|
|
111
105
|
ImageToVideoDurationSelectorProps,
|
|
112
|
-
ImageToVideoMusicMoodSelectorProps,
|
|
113
106
|
ImageToVideoSelectionGridProps,
|
|
114
107
|
ImageToVideoSelectionGridTranslations,
|
|
115
108
|
ImageToVideoGenerateButtonProps,
|
|
@@ -10,10 +10,6 @@ export type { AnimationStyleSelectorProps as ImageToVideoAnimationStyleSelectorP
|
|
|
10
10
|
export { DurationSelector as ImageToVideoDurationSelector } from "./DurationSelector";
|
|
11
11
|
export type { DurationSelectorProps as ImageToVideoDurationSelectorProps } from "./DurationSelector";
|
|
12
12
|
|
|
13
|
-
// Music Mood Selector
|
|
14
|
-
export { MusicMoodSelector as ImageToVideoMusicMoodSelector } from "./MusicMoodSelector";
|
|
15
|
-
export type { MusicMoodSelectorProps as ImageToVideoMusicMoodSelectorProps } from "./MusicMoodSelector";
|
|
16
|
-
|
|
17
13
|
// Image Selection Grid
|
|
18
14
|
export { ImageSelectionGrid as ImageToVideoSelectionGrid } from "./ImageSelectionGrid";
|
|
19
15
|
export type {
|
package/src/exports/features.ts
CHANGED
|
@@ -55,7 +55,7 @@ export {
|
|
|
55
55
|
|
|
56
56
|
// Image-to-Video Feature
|
|
57
57
|
export type {
|
|
58
|
-
AnimationStyle, AnimationStyleId,
|
|
58
|
+
AnimationStyle, AnimationStyleId,
|
|
59
59
|
VideoDuration, DurationOption as ImageToVideoDurationOption,
|
|
60
60
|
ImageToVideoFormState, ImageToVideoFormActions, ImageToVideoFormDefaults,
|
|
61
61
|
ImageToVideoCallbacks, ImageToVideoFormConfig, ImageToVideoTranslationsExtended,
|
|
@@ -68,18 +68,17 @@ export type {
|
|
|
68
68
|
UseImageToVideoFormOptions, UseImageToVideoFormReturn,
|
|
69
69
|
UseImageToVideoFeatureProps, UseImageToVideoFeatureReturn,
|
|
70
70
|
ImageToVideoAnimationStyleSelectorProps, ImageToVideoDurationSelectorProps,
|
|
71
|
-
|
|
71
|
+
ImageToVideoSelectionGridProps,
|
|
72
72
|
ImageToVideoSelectionGridTranslations, ImageToVideoGenerateButtonProps,
|
|
73
73
|
} from "../domains/image-to-video";
|
|
74
74
|
export {
|
|
75
75
|
IMAGE_TO_VIDEO_ANIMATION_STYLES, IMAGE_TO_VIDEO_DEFAULT_ANIMATION,
|
|
76
|
-
IMAGE_TO_VIDEO_MUSIC_MOODS, IMAGE_TO_VIDEO_DEFAULT_MUSIC,
|
|
77
76
|
IMAGE_TO_VIDEO_DURATION_OPTIONS, IMAGE_TO_VIDEO_DEFAULT_DURATION,
|
|
78
77
|
IMAGE_TO_VIDEO_FORM_DEFAULTS, IMAGE_TO_VIDEO_CONFIG,
|
|
79
78
|
executeImageToVideo, hasImageToVideoSupport,
|
|
80
79
|
useImageToVideoFormState, useImageToVideoGeneration, useImageToVideoForm, useImageToVideoFeature,
|
|
81
80
|
ImageToVideoAnimationStyleSelector, ImageToVideoDurationSelector,
|
|
82
|
-
|
|
81
|
+
ImageToVideoSelectionGrid, ImageToVideoGenerateButton,
|
|
83
82
|
} from "../domains/image-to-video";
|
|
84
83
|
|
|
85
84
|
// Wizard Flows
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Music Mood Constants
|
|
3
|
-
* Default music moods for image-to-video
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import type { MusicMood } from "../types";
|
|
7
|
-
|
|
8
|
-
export const DEFAULT_MUSIC_MOODS: MusicMood[] = [
|
|
9
|
-
{
|
|
10
|
-
id: "none",
|
|
11
|
-
name: "No Music",
|
|
12
|
-
description: "Silent video",
|
|
13
|
-
icon: "volume-mute-outline",
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
id: "energetic",
|
|
17
|
-
name: "Energetic",
|
|
18
|
-
description: "Upbeat and dynamic",
|
|
19
|
-
icon: "flash-outline",
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
id: "calm",
|
|
23
|
-
name: "Calm",
|
|
24
|
-
description: "Peaceful and relaxing",
|
|
25
|
-
icon: "leaf-outline",
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
id: "happy",
|
|
29
|
-
name: "Happy",
|
|
30
|
-
description: "Cheerful and joyful",
|
|
31
|
-
icon: "happy-outline",
|
|
32
|
-
},
|
|
33
|
-
{
|
|
34
|
-
id: "emotional",
|
|
35
|
-
name: "Emotional",
|
|
36
|
-
description: "Touching and heartfelt",
|
|
37
|
-
icon: "heart-outline",
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
id: "rhythmic",
|
|
41
|
-
name: "Rhythmic",
|
|
42
|
-
description: "Beat-driven and groovy",
|
|
43
|
-
icon: "musical-notes-outline",
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
id: "custom",
|
|
47
|
-
name: "Custom",
|
|
48
|
-
description: "Upload your own audio",
|
|
49
|
-
icon: "cloud-upload-outline",
|
|
50
|
-
},
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
export const DEFAULT_MUSIC_MOOD_ID = "none";
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Music Types for Image-to-Video
|
|
3
|
-
* Defines music mood options
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
export interface MusicMood {
|
|
7
|
-
id: string;
|
|
8
|
-
name: string;
|
|
9
|
-
description: string;
|
|
10
|
-
icon: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type MusicMoodId =
|
|
14
|
-
| "none"
|
|
15
|
-
| "energetic"
|
|
16
|
-
| "calm"
|
|
17
|
-
| "happy"
|
|
18
|
-
| "emotional"
|
|
19
|
-
| "rhythmic"
|
|
20
|
-
| "custom"
|
|
21
|
-
| string;
|
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Music Mood Selector Component
|
|
3
|
-
* Generic component for music mood selection
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import React from "react";
|
|
7
|
-
import { View, ScrollView, TouchableOpacity, StyleSheet } from "react-native";
|
|
8
|
-
import {
|
|
9
|
-
AtomicText,
|
|
10
|
-
AtomicIcon,
|
|
11
|
-
useAppDesignTokens,
|
|
12
|
-
} from "@umituz/react-native-design-system";
|
|
13
|
-
import type { MusicMood, MusicMoodId } from "../../domain/types";
|
|
14
|
-
|
|
15
|
-
export interface MusicMoodSelectorProps {
|
|
16
|
-
moods: MusicMood[];
|
|
17
|
-
selectedMood: MusicMoodId;
|
|
18
|
-
onMoodSelect: (moodId: MusicMoodId) => void;
|
|
19
|
-
label: string;
|
|
20
|
-
hasCustomAudio?: boolean;
|
|
21
|
-
customAudioLabel?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const MusicMoodSelector: React.FC<MusicMoodSelectorProps> = ({
|
|
25
|
-
moods,
|
|
26
|
-
selectedMood,
|
|
27
|
-
onMoodSelect,
|
|
28
|
-
label,
|
|
29
|
-
hasCustomAudio = false,
|
|
30
|
-
customAudioLabel,
|
|
31
|
-
}) => {
|
|
32
|
-
const tokens = useAppDesignTokens();
|
|
33
|
-
|
|
34
|
-
return (
|
|
35
|
-
<View style={componentStyles.section}>
|
|
36
|
-
<AtomicText
|
|
37
|
-
type="bodyMedium"
|
|
38
|
-
style={[componentStyles.label, { color: tokens.colors.textPrimary }]}
|
|
39
|
-
>
|
|
40
|
-
{label}
|
|
41
|
-
</AtomicText>
|
|
42
|
-
<ScrollView
|
|
43
|
-
horizontal
|
|
44
|
-
showsHorizontalScrollIndicator={false}
|
|
45
|
-
style={componentStyles.scroll}
|
|
46
|
-
>
|
|
47
|
-
{moods.map((mood) => {
|
|
48
|
-
const isSelected = selectedMood === mood.id;
|
|
49
|
-
return (
|
|
50
|
-
<TouchableOpacity
|
|
51
|
-
key={mood.id}
|
|
52
|
-
style={[
|
|
53
|
-
componentStyles.card,
|
|
54
|
-
{
|
|
55
|
-
backgroundColor: isSelected
|
|
56
|
-
? tokens.colors.primary
|
|
57
|
-
: tokens.colors.surface,
|
|
58
|
-
borderColor: isSelected
|
|
59
|
-
? tokens.colors.primary
|
|
60
|
-
: tokens.colors.borderLight,
|
|
61
|
-
},
|
|
62
|
-
]}
|
|
63
|
-
onPress={() => onMoodSelect(mood.id)}
|
|
64
|
-
activeOpacity={0.7}
|
|
65
|
-
>
|
|
66
|
-
<View
|
|
67
|
-
style={[
|
|
68
|
-
componentStyles.iconContainer,
|
|
69
|
-
{
|
|
70
|
-
backgroundColor: isSelected
|
|
71
|
-
? tokens.colors.modalOverlay
|
|
72
|
-
: tokens.colors.surface,
|
|
73
|
-
},
|
|
74
|
-
]}
|
|
75
|
-
>
|
|
76
|
-
<AtomicIcon
|
|
77
|
-
name={mood.icon as never}
|
|
78
|
-
size="lg"
|
|
79
|
-
color={isSelected ? "onSurface" : "primary"}
|
|
80
|
-
/>
|
|
81
|
-
</View>
|
|
82
|
-
<AtomicText
|
|
83
|
-
type="bodySmall"
|
|
84
|
-
style={[
|
|
85
|
-
componentStyles.moodName,
|
|
86
|
-
{
|
|
87
|
-
color: isSelected
|
|
88
|
-
? tokens.colors.textInverse
|
|
89
|
-
: tokens.colors.textPrimary,
|
|
90
|
-
},
|
|
91
|
-
]}
|
|
92
|
-
>
|
|
93
|
-
{mood.name}
|
|
94
|
-
</AtomicText>
|
|
95
|
-
<AtomicText
|
|
96
|
-
type="labelSmall"
|
|
97
|
-
style={[
|
|
98
|
-
componentStyles.moodDescription,
|
|
99
|
-
{
|
|
100
|
-
color: isSelected
|
|
101
|
-
? tokens.colors.textInverse
|
|
102
|
-
: tokens.colors.textSecondary,
|
|
103
|
-
opacity: isSelected ? 0.9 : 0.7,
|
|
104
|
-
},
|
|
105
|
-
]}
|
|
106
|
-
>
|
|
107
|
-
{mood.description}
|
|
108
|
-
</AtomicText>
|
|
109
|
-
</TouchableOpacity>
|
|
110
|
-
);
|
|
111
|
-
})}
|
|
112
|
-
</ScrollView>
|
|
113
|
-
{selectedMood === "custom" && hasCustomAudio && customAudioLabel && (
|
|
114
|
-
<View
|
|
115
|
-
style={[
|
|
116
|
-
componentStyles.customBadge,
|
|
117
|
-
{ backgroundColor: tokens.colors.surfaceVariant },
|
|
118
|
-
]}
|
|
119
|
-
>
|
|
120
|
-
<AtomicIcon name="checkmark-circle-outline" size="sm" color="primary" />
|
|
121
|
-
<AtomicText
|
|
122
|
-
type="labelSmall"
|
|
123
|
-
style={[componentStyles.customBadgeText, { color: tokens.colors.primary }]}
|
|
124
|
-
>
|
|
125
|
-
{customAudioLabel}
|
|
126
|
-
</AtomicText>
|
|
127
|
-
</View>
|
|
128
|
-
)}
|
|
129
|
-
</View>
|
|
130
|
-
);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
const componentStyles = StyleSheet.create({
|
|
134
|
-
section: {
|
|
135
|
-
padding: 16,
|
|
136
|
-
marginBottom: 8,
|
|
137
|
-
},
|
|
138
|
-
label: {
|
|
139
|
-
fontWeight: "600",
|
|
140
|
-
marginBottom: 12,
|
|
141
|
-
},
|
|
142
|
-
scroll: {
|
|
143
|
-
marginHorizontal: -16,
|
|
144
|
-
paddingHorizontal: 16,
|
|
145
|
-
},
|
|
146
|
-
card: {
|
|
147
|
-
width: 140,
|
|
148
|
-
padding: 16,
|
|
149
|
-
borderRadius: 16,
|
|
150
|
-
borderWidth: 2,
|
|
151
|
-
marginRight: 12,
|
|
152
|
-
alignItems: "center",
|
|
153
|
-
},
|
|
154
|
-
iconContainer: {
|
|
155
|
-
width: 56,
|
|
156
|
-
height: 56,
|
|
157
|
-
borderRadius: 28,
|
|
158
|
-
alignItems: "center",
|
|
159
|
-
justifyContent: "center",
|
|
160
|
-
},
|
|
161
|
-
moodName: {
|
|
162
|
-
fontWeight: "600",
|
|
163
|
-
marginTop: 8,
|
|
164
|
-
textAlign: "center",
|
|
165
|
-
},
|
|
166
|
-
moodDescription: {
|
|
167
|
-
marginTop: 4,
|
|
168
|
-
textAlign: "center",
|
|
169
|
-
},
|
|
170
|
-
customBadge: {
|
|
171
|
-
flexDirection: "row",
|
|
172
|
-
alignItems: "center",
|
|
173
|
-
padding: 12,
|
|
174
|
-
borderRadius: 12,
|
|
175
|
-
marginTop: 12,
|
|
176
|
-
},
|
|
177
|
-
customBadgeText: {
|
|
178
|
-
marginLeft: 8,
|
|
179
|
-
flex: 1,
|
|
180
|
-
},
|
|
181
|
-
});
|