@onairos/react-native 3.1.1 → 3.1.3

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.
@@ -30,10 +30,13 @@ const TrainingModal = ({
30
30
  const [socketConnected, setSocketConnected] = (0, _react.useState)(false);
31
31
  const [trainingStatus, setTrainingStatus] = (0, _react.useState)('Initializing...');
32
32
  const [hasError, setHasError] = (0, _react.useState)(false);
33
+ const [errorCode, setErrorCode] = (0, _react.useState)(null);
33
34
  const [isTrainingComplete, setIsTrainingComplete] = (0, _react.useState)(false);
34
35
  const [userTraits, setUserTraits] = (0, _react.useState)(null);
35
36
  const [inferenceResults, setInferenceResults] = (0, _react.useState)(null);
36
37
  const [internalProgress, setInternalProgress] = (0, _react.useState)(0);
38
+ const [isEnochSDK, setIsEnochSDK] = (0, _react.useState)(false); // Determine if using Enoch SDK
39
+ const [trainingResults, setTrainingResults] = (0, _react.useState)(null);
37
40
 
38
41
  // Use internal progress if available, otherwise fall back to prop progress
39
42
  const currentProgress = internalProgress > 0 ? internalProgress / 100 : progress;
@@ -48,115 +51,193 @@ const TrainingModal = ({
48
51
  name: username || 'mobile_user'
49
52
  };
50
53
 
51
- // Start Enoch training via API
52
- const startEnochTraining = async socketId => {
54
+ // Check if we should use Enoch SDK (based on modelKey or other criteria)
55
+ (0, _react.useEffect)(() => {
56
+ // For now, default to clean training unless specified
57
+ // In real implementation, this would be determined by SDK type
58
+ setIsEnochSDK((modelKey === null || modelKey === void 0 ? void 0 : modelKey.includes('enoch')) || false);
59
+ }, [modelKey]);
60
+
61
+ // Start training with new API schema
62
+ const startTraining = async socketId => {
53
63
  try {
54
64
  setTrainingStatus('Starting training...');
55
65
  setInternalProgress(10);
66
+ setHasError(false);
67
+ setErrorCode(null);
56
68
  if (!userToken) {
57
69
  throw new Error('No authentication token available');
58
70
  }
59
- console.log('🚀 Starting Enoch training with socketId:', socketId);
60
- console.log('🔍 Production mode: Using live training API');
61
71
 
62
- // Prepare user data for training
72
+ // Determine endpoint based on SDK type
73
+ const endpoint = isEnochSDK ? '/mobile-training/enoch' : '/mobile-training/clean';
74
+ console.log(`🚀 Starting training with endpoint: ${endpoint}`);
75
+ console.log('🔍 Production mode: Using new training API schema');
76
+
77
+ // Prepare training data in new format
63
78
  const trainingData = {
64
- socketId,
65
- username: (userInfo === null || userInfo === void 0 ? void 0 : userInfo.username) || (userInfo === null || userInfo === void 0 ? void 0 : userInfo.name) || username || 'mobile_user',
66
- email: (userInfo === null || userInfo === void 0 ? void 0 : userInfo.email) || null,
67
- modelKey: modelKey || null,
68
- userId: (userInfo === null || userInfo === void 0 ? void 0 : userInfo.id) || null
79
+ Info: {
80
+ username: (userInfo === null || userInfo === void 0 ? void 0 : userInfo.username) || (userInfo === null || userInfo === void 0 ? void 0 : userInfo.name) || username || 'mobile_user',
81
+ // Mock connected platforms - in real implementation this would come from props
82
+ connectedPlatforms: ['youtube', 'instagram', 'reddit'] // TODO: Get from actual connections
83
+ }
69
84
  };
70
85
  console.log('📤 Sending training data:', trainingData);
71
86
 
72
- // Call the actual training API - backend confirmed this is working
73
- const response = await fetch('https://api2.onairos.uk/enoch/trainModel/mobile', {
87
+ // Call the new training API
88
+ const response = await fetch(`https://api2.onairos.uk${endpoint}`, {
74
89
  method: 'POST',
75
90
  headers: {
76
91
  'Content-Type': 'application/json',
77
- 'Authorization': `Bearer ${userToken || 'temp-token'}` // Backend has JWT auth working
92
+ 'Authorization': `Bearer ${userToken}` // JWT token authentication
78
93
  },
79
94
  body: JSON.stringify(trainingData)
80
95
  });
81
96
  const result = await response.json();
82
97
  if (result.success) {
83
98
  console.log('🚀 Training Started:', result.message);
84
- setTrainingStatus('Training model...');
99
+ setTrainingStatus('Training initiated...');
85
100
  setInternalProgress(20);
86
101
  } else {
87
102
  console.error('Training start failed:', result.error);
88
- setTrainingStatus(`Error: ${result.error}`);
89
- setHasError(true);
90
103
 
91
- // In production mode, don't fallback to simulation on API failure
92
- console.error('🚨 Production mode: Training failed, not falling back to simulation');
104
+ // Handle specific error codes from new schema
105
+ if (result.code) {
106
+ setErrorCode(result.code);
107
+ handleTrainingError(result.code, result.error);
108
+ } else {
109
+ setTrainingStatus(`Error: ${result.error}`);
110
+ setHasError(true);
111
+ }
93
112
  }
94
113
  } catch (error) {
95
114
  console.error('Training start error:', error);
96
115
  setTrainingStatus(`Error: ${error instanceof Error ? error.message : 'Unknown error'}`);
97
116
  setHasError(true);
117
+ }
118
+ };
98
119
 
99
- // In production mode, show the actual error
100
- console.error('🚨 Production mode: Training failed with error:', error);
120
+ // Handle training errors with specific error codes
121
+ const handleTrainingError = (code, message) => {
122
+ console.error(`Training Error [${code}]:`, message);
123
+ setHasError(true);
124
+ switch (code) {
125
+ case 'PIN_REQUIRED':
126
+ setTrainingStatus('PIN setup required');
127
+ _reactNative.Alert.alert('PIN Required', 'Please set up your PIN first before training.', [{
128
+ text: 'Cancel',
129
+ style: 'cancel'
130
+ }, {
131
+ text: 'Set PIN',
132
+ onPress: () => {
133
+ // TODO: Navigate to PIN setup
134
+ console.log('Navigate to PIN setup');
135
+ }
136
+ }]);
137
+ break;
138
+ case 'CONNECTIONS_REQUIRED':
139
+ setTrainingStatus('No connected accounts');
140
+ _reactNative.Alert.alert('Connections Required', 'Please connect at least one social media account before training.', [{
141
+ text: 'Cancel',
142
+ style: 'cancel'
143
+ }, {
144
+ text: 'Connect',
145
+ onPress: () => {
146
+ // TODO: Navigate to connections
147
+ console.log('Navigate to connections');
148
+ }
149
+ }]);
150
+ break;
151
+ case 'INSUFFICIENT_DATA':
152
+ setTrainingStatus('Insufficient data for training');
153
+ _reactNative.Alert.alert('Insufficient Data', 'We need more interaction data to train your model effectively. Please use your connected platforms more and try again.', [{
154
+ text: 'OK',
155
+ style: 'default'
156
+ }]);
157
+ break;
158
+ case 'ENCRYPTION_REQUIRED':
159
+ setTrainingStatus('Model encryption failed');
160
+ _reactNative.Alert.alert('Encryption Error', 'Failed to encrypt your model. Please try again.', [{
161
+ text: 'OK',
162
+ style: 'default'
163
+ }]);
164
+ break;
165
+ default:
166
+ setTrainingStatus(`Error: ${message}`);
167
+ _reactNative.Alert.alert('Training Error', message || 'An unexpected error occurred during training.', [{
168
+ text: 'OK',
169
+ style: 'default'
170
+ }]);
101
171
  }
102
172
  };
103
173
 
104
174
  // Simulate training progress for test mode
105
175
  const simulateTraining = () => {
106
- var _stages$currentStage;
107
- console.log('🧪 Starting simulated training...');
108
- setTrainingStatus('Initializing training...');
109
- setInternalProgress(10);
176
+ var _phases$currentPhase;
177
+ console.log('🧪 Starting simulated training with new schema phases...');
178
+ setTrainingStatus('Validating Requirements');
179
+ setInternalProgress(5);
110
180
 
111
- // Fast training for test mode (shorter delays)
112
- const baseDelay = 800;
113
- const stages = [{
114
- progress: 20,
115
- status: 'Analyzing data patterns...',
116
- delay: baseDelay
181
+ // Simulate all phases from the new schema
182
+ const phases = [{
183
+ progress: 10,
184
+ status: 'Validating Requirements',
185
+ delay: 1000
117
186
  }, {
118
- progress: 35,
119
- status: 'Building neural network...',
120
- delay: baseDelay
187
+ progress: 25,
188
+ status: 'Training Model',
189
+ delay: 2000
121
190
  }, {
122
191
  progress: 50,
123
- status: 'Training model...',
124
- delay: baseDelay
192
+ status: 'Training Model',
193
+ delay: 2000
125
194
  }, {
126
195
  progress: 65,
127
- status: 'Optimizing parameters...',
128
- delay: baseDelay
196
+ status: 'Model Trained - Running Test Inference',
197
+ delay: 1500
129
198
  }, {
130
199
  progress: 80,
131
- status: 'Running test inference...',
132
- delay: baseDelay
200
+ status: 'Encrypting and Compressing Model',
201
+ delay: 1000
133
202
  }, {
134
203
  progress: 95,
135
- status: 'Finalizing model...',
136
- delay: baseDelay
204
+ status: 'Uploading Encrypted Model to ARDrive',
205
+ delay: 1500
137
206
  }, {
138
207
  progress: 100,
139
208
  status: 'Complete!',
140
- delay: 300
209
+ delay: 500
141
210
  }];
142
- let currentStage = 0;
211
+ let currentPhase = 0;
143
212
  const progressInterval = setInterval(() => {
144
- if (currentStage < stages.length) {
145
- const stage = stages[currentStage];
146
- setInternalProgress(stage.progress);
147
- setTrainingStatus(stage.status);
148
- if (stage.progress === 100) {
213
+ if (currentPhase < phases.length) {
214
+ const phase = phases[currentPhase];
215
+ setInternalProgress(phase.progress);
216
+ setTrainingStatus(phase.status);
217
+ if (phase.progress === 100) {
149
218
  setIsTrainingComplete(true);
150
219
  clearInterval(progressInterval);
151
220
 
221
+ // Mock completion results
222
+ const mockResults = {
223
+ completed: true,
224
+ message: "Model trained, encrypted, and stored successfully",
225
+ storage: "ARDrive",
226
+ encryption: true,
227
+ inference: isEnochSDK,
228
+ // Only Enoch SDK runs inference
229
+ modelUrl: `https://arweave.net/mock_tx_${Date.now()}`
230
+ };
231
+ setTrainingResults(mockResults);
232
+
152
233
  // Auto-complete after a short delay
153
234
  setTimeout(() => {
154
235
  onComplete && onComplete();
155
- }, 800);
236
+ }, 1000);
156
237
  }
157
- currentStage++;
238
+ currentPhase++;
158
239
  }
159
- }, ((_stages$currentStage = stages[currentStage]) === null || _stages$currentStage === void 0 ? void 0 : _stages$currentStage.delay) || baseDelay);
240
+ }, ((_phases$currentPhase = phases[currentPhase]) === null || _phases$currentPhase === void 0 ? void 0 : _phases$currentPhase.delay) || 1000);
160
241
 
161
242
  // Cleanup interval on unmount
162
243
  return () => clearInterval(progressInterval);
@@ -168,16 +249,17 @@ const TrainingModal = ({
168
249
  console.log('Setting up socket connection for training...');
169
250
  console.log('🧑‍💻 User info available:', userInfo);
170
251
  console.log('🧪 Test mode:', test);
252
+ console.log('🔧 Using Enoch SDK:', isEnochSDK);
171
253
 
172
254
  // If test mode is enabled, use simulation instead of real API
173
255
  if (test) {
174
- console.log('🧪 Test mode enabled - Using simulated training');
256
+ console.log('🧪 Test mode enabled - Using simulated training with new schema');
175
257
  setSocketConnected(true);
176
258
  simulateTraining();
177
259
  return;
178
260
  }
179
261
 
180
- // Initialize real socket connection - backend confirmed this is working
262
+ // Initialize real socket connection
181
263
  try {
182
264
  console.log('🔌 Production mode: Initializing real socket connection');
183
265
 
@@ -187,14 +269,14 @@ const TrainingModal = ({
187
269
  autoConnect: false
188
270
  });
189
271
 
190
- // Socket event listeners
272
+ // Socket event listeners for new schema
191
273
  socketRef.current.on('connect', () => {
192
274
  var _socketRef$current;
193
275
  console.log('✅ Socket connected for training');
194
276
  setSocketConnected(true);
195
277
  const socketId = (_socketRef$current = socketRef.current) === null || _socketRef$current === void 0 ? void 0 : _socketRef$current.id;
196
278
  if (socketId) {
197
- startEnochTraining(socketId);
279
+ startTraining(socketId);
198
280
  }
199
281
  });
200
282
  socketRef.current.on('disconnect', () => {
@@ -206,38 +288,88 @@ const TrainingModal = ({
206
288
  setTrainingStatus('Connection failed');
207
289
  setHasError(true);
208
290
  });
291
+
292
+ // Handle training updates with new schema phases
293
+ socketRef.current.on('trainingUpdate', data => {
294
+ console.log('📡 Training update received:', data);
295
+ if (data.error) {
296
+ if (data.code) {
297
+ setErrorCode(data.code);
298
+ handleTrainingError(data.code, data.error);
299
+ } else {
300
+ console.error('Training update error:', data.error);
301
+ setTrainingStatus(`Error: ${data.error}`);
302
+ setHasError(true);
303
+ }
304
+ } else {
305
+ // Handle different status phases
306
+ if (data.status) {
307
+ setTrainingStatus(data.status);
308
+
309
+ // Update progress based on phase
310
+ switch (data.status) {
311
+ case 'Validating Requirements':
312
+ setInternalProgress(10);
313
+ break;
314
+ case 'Training Model':
315
+ setInternalProgress(data.percent || 40);
316
+ break;
317
+ case 'Model Trained - Running Test Inference':
318
+ setInternalProgress(65);
319
+ break;
320
+ case 'Encrypting and Compressing Model':
321
+ setInternalProgress(80);
322
+ break;
323
+ case 'Uploading Encrypted Model to ARDrive':
324
+ setInternalProgress(90);
325
+ break;
326
+ default:
327
+ if (data.percent) {
328
+ setInternalProgress(data.percent);
329
+ }
330
+ }
331
+ }
332
+ if (data.progress) {
333
+ setInternalProgress(data.progress);
334
+ }
335
+ }
336
+ });
337
+
338
+ // Handle training completion with new schema
209
339
  socketRef.current.on('trainingCompleted', data => {
210
340
  console.log('✅ Training Complete:', data);
211
- setTrainingStatus('Running test inference...');
212
- setInternalProgress(60);
341
+ if (data.completed && data.storage === 'ARDrive' && data.encryption === true) {
342
+ setTrainingStatus('Training completed successfully!');
343
+ setIsTrainingComplete(true);
344
+ setInternalProgress(100);
345
+ setTrainingResults(data);
346
+
347
+ // Display results
348
+ console.log('✅ Model URL:', data.modelUrl);
349
+ console.log('✅ Storage:', data.storage);
350
+ console.log('✅ Encrypted:', data.encryption);
351
+ console.log('✅ Inference:', data.inference);
352
+
353
+ // Auto-complete after a short delay
354
+ setTimeout(() => {
355
+ onComplete && onComplete();
356
+ }, 1500);
357
+ } else {
358
+ console.error('Training completion missing required fields:', data);
359
+ setTrainingStatus('Training completed with warnings');
360
+ setHasError(true);
361
+ }
213
362
  });
363
+
364
+ // Legacy handlers for backward compatibility
214
365
  socketRef.current.on('inferenceCompleted', data => {
215
366
  console.log('🧠 Inference Complete:', data);
216
- setTrainingStatus('Uploading to S3...');
217
- setInternalProgress(80);
218
367
  setUserTraits(data.traits);
219
368
  setInferenceResults(data.inferenceResults);
220
369
  });
221
370
  socketRef.current.on('modelStandby', data => {
222
- console.log('🎉 All Complete:', data);
223
- setIsTrainingComplete(true);
224
- setTrainingStatus('Complete!');
225
- setInternalProgress(100);
226
-
227
- // Auto-complete after a short delay
228
- setTimeout(() => {
229
- onComplete && onComplete();
230
- }, 1500);
231
- });
232
- socketRef.current.on('trainingUpdate', data => {
233
- if (data.error) {
234
- console.error('Training update error:', data.error);
235
- setTrainingStatus(`Error: ${data.error}`);
236
- setHasError(true);
237
- } else if (data.progress) {
238
- setInternalProgress(data.progress);
239
- setTrainingStatus(data.status || 'Training in progress...');
240
- }
371
+ console.log('🎉 Model Ready:', data);
372
+ // This is handled by trainingCompleted now
241
373
  });
242
374
 
243
375
  // Connect to socket
@@ -256,7 +388,47 @@ const TrainingModal = ({
256
388
  socketRef.current = null;
257
389
  }
258
390
  };
259
- }, [visible, userInfo, test]);
391
+ }, [visible, userInfo, test, isEnochSDK]);
392
+
393
+ // Render error details for specific error codes
394
+ const renderErrorDetails = () => {
395
+ if (!hasError || !errorCode) return null;
396
+ const errorDetails = {
397
+ 'PIN_REQUIRED': {
398
+ icon: 'lock',
399
+ title: 'PIN Setup Required',
400
+ description: 'Set up your PIN to secure your AI model'
401
+ },
402
+ 'CONNECTIONS_REQUIRED': {
403
+ icon: 'link',
404
+ title: 'Connect Accounts',
405
+ description: 'Connect at least one social media account'
406
+ },
407
+ 'INSUFFICIENT_DATA': {
408
+ icon: 'data-usage',
409
+ title: 'More Data Needed',
410
+ description: 'We need more interaction data to train effectively'
411
+ },
412
+ 'ENCRYPTION_REQUIRED': {
413
+ icon: 'security',
414
+ title: 'Encryption Failed',
415
+ description: 'Failed to encrypt your model securely'
416
+ }
417
+ };
418
+ const details = errorDetails[errorCode];
419
+ if (!details) return null;
420
+ return /*#__PURE__*/_react.default.createElement(_reactNative.View, {
421
+ style: styles.errorDetailsContainer
422
+ }, /*#__PURE__*/_react.default.createElement(_MaterialIcons.default, {
423
+ name: details.icon,
424
+ size: 32,
425
+ color: "#FF6B6B"
426
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
427
+ style: styles.errorTitle
428
+ }, details.title), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
429
+ style: styles.errorDescription
430
+ }, details.description));
431
+ };
260
432
  return /*#__PURE__*/_react.default.createElement(_reactNative.Modal, {
261
433
  visible: visible,
262
434
  transparent: true,
@@ -281,7 +453,7 @@ const TrainingModal = ({
281
453
  resizeMode: "contain"
282
454
  }), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
283
455
  style: styles.title
284
- }, "Training Your AI"), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
456
+ }, isEnochSDK ? 'Training AI with Inference' : 'Training Your AI'), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
285
457
  style: styles.progressContainer
286
458
  }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
287
459
  style: styles.progressBar
@@ -301,7 +473,41 @@ const TrainingModal = ({
301
473
  color: "#FF6B6B"
302
474
  }), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
303
475
  style: styles.errorText
304
- }, "Training encountered an error")), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
476
+ }, "Training encountered an error")), renderErrorDetails(), isTrainingComplete && trainingResults && /*#__PURE__*/_react.default.createElement(_reactNative.View, {
477
+ style: styles.resultsContainer
478
+ }, /*#__PURE__*/_react.default.createElement(_MaterialIcons.default, {
479
+ name: "check-circle",
480
+ size: 32,
481
+ color: "#4CAF50"
482
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
483
+ style: styles.resultsTitle
484
+ }, "Training Complete!"), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
485
+ style: styles.resultsDetails
486
+ }, /*#__PURE__*/_react.default.createElement(_reactNative.View, {
487
+ style: styles.resultItem
488
+ }, /*#__PURE__*/_react.default.createElement(_MaterialIcons.default, {
489
+ name: "cloud-done",
490
+ size: 16,
491
+ color: "#666"
492
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
493
+ style: styles.resultText
494
+ }, "Storage: ", trainingResults.storage)), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
495
+ style: styles.resultItem
496
+ }, /*#__PURE__*/_react.default.createElement(_MaterialIcons.default, {
497
+ name: "security",
498
+ size: 16,
499
+ color: "#666"
500
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
501
+ style: styles.resultText
502
+ }, "Encrypted: ", trainingResults.encryption ? 'Yes' : 'No')), trainingResults.inference && /*#__PURE__*/_react.default.createElement(_reactNative.View, {
503
+ style: styles.resultItem
504
+ }, /*#__PURE__*/_react.default.createElement(_MaterialIcons.default, {
505
+ name: "psychology",
506
+ size: 16,
507
+ color: "#666"
508
+ }), /*#__PURE__*/_react.default.createElement(_reactNative.Text, {
509
+ style: styles.resultText
510
+ }, "Inference: Completed")))), /*#__PURE__*/_react.default.createElement(_reactNative.View, {
305
511
  style: styles.footer
306
512
  }, /*#__PURE__*/_react.default.createElement(_reactNative.TouchableOpacity, {
307
513
  style: styles.cancelButton,
@@ -371,7 +577,8 @@ const styles = _reactNative.StyleSheet.create({
371
577
  statusText: {
372
578
  fontSize: 14,
373
579
  color: _constants.COLORS.text.secondary,
374
- marginBottom: 24
580
+ marginBottom: 24,
581
+ textAlign: 'center'
375
582
  },
376
583
  progressContainer: {
377
584
  width: '100%',
@@ -448,12 +655,63 @@ const styles = _reactNative.StyleSheet.create({
448
655
  errorContainer: {
449
656
  flexDirection: 'row',
450
657
  alignItems: 'center',
451
- marginBottom: 24
658
+ marginBottom: 16
452
659
  },
453
660
  errorText: {
454
661
  fontSize: 14,
455
662
  color: '#FF6B6B',
456
663
  marginLeft: 8
664
+ },
665
+ errorDetailsContainer: {
666
+ alignItems: 'center',
667
+ marginBottom: 24,
668
+ padding: 16,
669
+ backgroundColor: '#FFF5F5',
670
+ borderRadius: 12,
671
+ borderWidth: 1,
672
+ borderColor: '#FFEBEE'
673
+ },
674
+ errorTitle: {
675
+ fontSize: 16,
676
+ fontWeight: '600',
677
+ color: '#FF6B6B',
678
+ marginTop: 8,
679
+ marginBottom: 4
680
+ },
681
+ errorDescription: {
682
+ fontSize: 14,
683
+ color: '#666',
684
+ textAlign: 'center'
685
+ },
686
+ resultsContainer: {
687
+ alignItems: 'center',
688
+ marginBottom: 24,
689
+ padding: 16,
690
+ backgroundColor: '#F5F5F5',
691
+ borderRadius: 12,
692
+ borderWidth: 1,
693
+ borderColor: '#E0E0E0'
694
+ },
695
+ resultsTitle: {
696
+ fontSize: 16,
697
+ fontWeight: '600',
698
+ color: '#4CAF50',
699
+ marginTop: 8,
700
+ marginBottom: 12
701
+ },
702
+ resultsDetails: {
703
+ alignItems: 'flex-start',
704
+ width: '100%'
705
+ },
706
+ resultItem: {
707
+ flexDirection: 'row',
708
+ alignItems: 'center',
709
+ marginBottom: 4
710
+ },
711
+ resultText: {
712
+ fontSize: 14,
713
+ color: '#666',
714
+ marginLeft: 8
457
715
  }
458
716
  });
459
717
  //# sourceMappingURL=TrainingModal.js.map