easy_ml 0.1.4 → 0.2.0.pre.rc1
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.
- checksums.yaml +4 -4
- data/README.md +234 -26
- data/Rakefile +45 -0
- data/app/controllers/easy_ml/application_controller.rb +67 -0
- data/app/controllers/easy_ml/columns_controller.rb +38 -0
- data/app/controllers/easy_ml/datasets_controller.rb +156 -0
- data/app/controllers/easy_ml/datasources_controller.rb +88 -0
- data/app/controllers/easy_ml/deploys_controller.rb +20 -0
- data/app/controllers/easy_ml/models_controller.rb +151 -0
- data/app/controllers/easy_ml/retraining_runs_controller.rb +19 -0
- data/app/controllers/easy_ml/settings_controller.rb +59 -0
- data/app/frontend/components/AlertProvider.tsx +108 -0
- data/app/frontend/components/DatasetPreview.tsx +161 -0
- data/app/frontend/components/EmptyState.tsx +28 -0
- data/app/frontend/components/ModelCard.tsx +255 -0
- data/app/frontend/components/ModelDetails.tsx +334 -0
- data/app/frontend/components/ModelForm.tsx +384 -0
- data/app/frontend/components/Navigation.tsx +300 -0
- data/app/frontend/components/Pagination.tsx +72 -0
- data/app/frontend/components/Popover.tsx +55 -0
- data/app/frontend/components/PredictionStream.tsx +105 -0
- data/app/frontend/components/ScheduleModal.tsx +726 -0
- data/app/frontend/components/SearchInput.tsx +23 -0
- data/app/frontend/components/SearchableSelect.tsx +132 -0
- data/app/frontend/components/dataset/AutosaveIndicator.tsx +39 -0
- data/app/frontend/components/dataset/ColumnConfigModal.tsx +431 -0
- data/app/frontend/components/dataset/ColumnFilters.tsx +256 -0
- data/app/frontend/components/dataset/ColumnList.tsx +101 -0
- data/app/frontend/components/dataset/FeatureConfigPopover.tsx +57 -0
- data/app/frontend/components/dataset/FeaturePicker.tsx +205 -0
- data/app/frontend/components/dataset/PreprocessingConfig.tsx +704 -0
- data/app/frontend/components/dataset/SplitConfigurator.tsx +120 -0
- data/app/frontend/components/dataset/splitters/DateSplitter.tsx +58 -0
- data/app/frontend/components/dataset/splitters/KFoldSplitter.tsx +68 -0
- data/app/frontend/components/dataset/splitters/LeavePOutSplitter.tsx +29 -0
- data/app/frontend/components/dataset/splitters/PredefinedSplitter.tsx +146 -0
- data/app/frontend/components/dataset/splitters/RandomSplitter.tsx +85 -0
- data/app/frontend/components/dataset/splitters/StratifiedSplitter.tsx +79 -0
- data/app/frontend/components/dataset/splitters/constants.ts +77 -0
- data/app/frontend/components/dataset/splitters/types.ts +168 -0
- data/app/frontend/components/dataset/splitters/utils.ts +53 -0
- data/app/frontend/components/features/CodeEditor.tsx +46 -0
- data/app/frontend/components/features/DataPreview.tsx +150 -0
- data/app/frontend/components/features/FeatureCard.tsx +88 -0
- data/app/frontend/components/features/FeatureForm.tsx +235 -0
- data/app/frontend/components/features/FeatureGroupCard.tsx +54 -0
- data/app/frontend/components/settings/PluginSettings.tsx +81 -0
- data/app/frontend/components/ui/badge.tsx +44 -0
- data/app/frontend/components/ui/collapsible.tsx +9 -0
- data/app/frontend/components/ui/scroll-area.tsx +46 -0
- data/app/frontend/components/ui/separator.tsx +29 -0
- data/app/frontend/entrypoints/App.tsx +40 -0
- data/app/frontend/entrypoints/Application.tsx +24 -0
- data/app/frontend/hooks/useAutosave.ts +61 -0
- data/app/frontend/layouts/Layout.tsx +38 -0
- data/app/frontend/lib/utils.ts +6 -0
- data/app/frontend/mockData.ts +272 -0
- data/app/frontend/pages/DatasetDetailsPage.tsx +103 -0
- data/app/frontend/pages/DatasetsPage.tsx +261 -0
- data/app/frontend/pages/DatasourceFormPage.tsx +147 -0
- data/app/frontend/pages/DatasourcesPage.tsx +261 -0
- data/app/frontend/pages/EditModelPage.tsx +45 -0
- data/app/frontend/pages/EditTransformationPage.tsx +56 -0
- data/app/frontend/pages/ModelsPage.tsx +115 -0
- data/app/frontend/pages/NewDatasetPage.tsx +366 -0
- data/app/frontend/pages/NewModelPage.tsx +45 -0
- data/app/frontend/pages/NewTransformationPage.tsx +43 -0
- data/app/frontend/pages/SettingsPage.tsx +272 -0
- data/app/frontend/pages/ShowModelPage.tsx +30 -0
- data/app/frontend/pages/TransformationsPage.tsx +95 -0
- data/app/frontend/styles/application.css +100 -0
- data/app/frontend/types/dataset.ts +146 -0
- data/app/frontend/types/datasource.ts +33 -0
- data/app/frontend/types/preprocessing.ts +1 -0
- data/app/frontend/types.ts +113 -0
- data/app/helpers/easy_ml/application_helper.rb +10 -0
- data/app/jobs/easy_ml/application_job.rb +21 -0
- data/app/jobs/easy_ml/batch_job.rb +46 -0
- data/app/jobs/easy_ml/compute_feature_job.rb +19 -0
- data/app/jobs/easy_ml/deploy_job.rb +13 -0
- data/app/jobs/easy_ml/finalize_feature_job.rb +15 -0
- data/app/jobs/easy_ml/refresh_dataset_job.rb +32 -0
- data/app/jobs/easy_ml/schedule_retraining_job.rb +11 -0
- data/app/jobs/easy_ml/sync_datasource_job.rb +17 -0
- data/app/jobs/easy_ml/training_job.rb +62 -0
- data/app/models/easy_ml/adapters/base_adapter.rb +45 -0
- data/app/models/easy_ml/adapters/polars_adapter.rb +77 -0
- data/app/models/easy_ml/cleaner.rb +82 -0
- data/app/models/easy_ml/column.rb +124 -0
- data/app/models/easy_ml/column_history.rb +30 -0
- data/app/models/easy_ml/column_list.rb +122 -0
- data/app/models/easy_ml/concerns/configurable.rb +61 -0
- data/app/models/easy_ml/concerns/versionable.rb +19 -0
- data/app/models/easy_ml/dataset.rb +767 -0
- data/app/models/easy_ml/dataset_history.rb +56 -0
- data/app/models/easy_ml/datasource.rb +182 -0
- data/app/models/easy_ml/datasource_history.rb +24 -0
- data/app/models/easy_ml/datasources/base_datasource.rb +54 -0
- data/app/models/easy_ml/datasources/file_datasource.rb +58 -0
- data/app/models/easy_ml/datasources/polars_datasource.rb +89 -0
- data/app/models/easy_ml/datasources/s3_datasource.rb +97 -0
- data/app/models/easy_ml/deploy.rb +114 -0
- data/app/models/easy_ml/event.rb +79 -0
- data/app/models/easy_ml/feature.rb +437 -0
- data/app/models/easy_ml/feature_history.rb +38 -0
- data/app/models/easy_ml/model.rb +575 -41
- data/app/models/easy_ml/model_file.rb +133 -0
- data/app/models/easy_ml/model_file_history.rb +24 -0
- data/app/models/easy_ml/model_history.rb +51 -0
- data/app/models/easy_ml/models/base_model.rb +58 -0
- data/app/models/easy_ml/models/hyperparameters/base.rb +99 -0
- data/app/models/easy_ml/models/hyperparameters/xgboost/dart.rb +82 -0
- data/app/models/easy_ml/models/hyperparameters/xgboost/gblinear.rb +82 -0
- data/app/models/easy_ml/models/hyperparameters/xgboost/gbtree.rb +97 -0
- data/app/models/easy_ml/models/hyperparameters/xgboost.rb +71 -0
- data/app/models/easy_ml/models/xgboost/evals_callback.rb +138 -0
- data/app/models/easy_ml/models/xgboost/progress_callback.rb +39 -0
- data/app/models/easy_ml/models/xgboost.rb +544 -5
- data/app/models/easy_ml/prediction.rb +44 -0
- data/app/models/easy_ml/retraining_job.rb +278 -0
- data/app/models/easy_ml/retraining_run.rb +184 -0
- data/app/models/easy_ml/settings.rb +37 -0
- data/app/models/easy_ml/splitter.rb +90 -0
- data/app/models/easy_ml/splitters/base_splitter.rb +28 -0
- data/app/models/easy_ml/splitters/date_splitter.rb +91 -0
- data/app/models/easy_ml/splitters/predefined_splitter.rb +74 -0
- data/app/models/easy_ml/splitters/random_splitter.rb +82 -0
- data/app/models/easy_ml/tuner_job.rb +56 -0
- data/app/models/easy_ml/tuner_run.rb +31 -0
- data/app/models/splitter_history.rb +6 -0
- data/app/serializers/easy_ml/column_serializer.rb +27 -0
- data/app/serializers/easy_ml/dataset_serializer.rb +73 -0
- data/app/serializers/easy_ml/datasource_serializer.rb +64 -0
- data/app/serializers/easy_ml/feature_serializer.rb +27 -0
- data/app/serializers/easy_ml/model_serializer.rb +90 -0
- data/app/serializers/easy_ml/retraining_job_serializer.rb +22 -0
- data/app/serializers/easy_ml/retraining_run_serializer.rb +39 -0
- data/app/serializers/easy_ml/settings_serializer.rb +9 -0
- data/app/views/layouts/easy_ml/application.html.erb +15 -0
- data/config/initializers/resque.rb +3 -0
- data/config/resque-pool.yml +6 -0
- data/config/routes.rb +39 -0
- data/config/spring.rb +1 -0
- data/config/vite.json +15 -0
- data/lib/easy_ml/configuration.rb +64 -0
- data/lib/easy_ml/core/evaluators/base_evaluator.rb +53 -0
- data/lib/easy_ml/core/evaluators/classification_evaluators.rb +126 -0
- data/lib/easy_ml/core/evaluators/regression_evaluators.rb +66 -0
- data/lib/easy_ml/core/model_evaluator.rb +161 -89
- data/lib/easy_ml/core/tuner/adapters/base_adapter.rb +28 -18
- data/lib/easy_ml/core/tuner/adapters/xgboost_adapter.rb +4 -25
- data/lib/easy_ml/core/tuner.rb +123 -62
- data/lib/easy_ml/core.rb +0 -3
- data/lib/easy_ml/core_ext/hash.rb +24 -0
- data/lib/easy_ml/core_ext/pathname.rb +11 -5
- data/lib/easy_ml/data/date_converter.rb +90 -0
- data/lib/easy_ml/data/filter_extensions.rb +31 -0
- data/lib/easy_ml/data/polars_column.rb +126 -0
- data/lib/easy_ml/data/polars_reader.rb +297 -0
- data/lib/easy_ml/data/preprocessor.rb +280 -142
- data/lib/easy_ml/data/simple_imputer.rb +255 -0
- data/lib/easy_ml/data/splits/file_split.rb +252 -0
- data/lib/easy_ml/data/splits/in_memory_split.rb +54 -0
- data/lib/easy_ml/data/splits/split.rb +95 -0
- data/lib/easy_ml/data/splits.rb +9 -0
- data/lib/easy_ml/data/statistics_learner.rb +93 -0
- data/lib/easy_ml/data/synced_directory.rb +341 -0
- data/lib/easy_ml/data.rb +6 -2
- data/lib/easy_ml/engine.rb +105 -6
- data/lib/easy_ml/feature_store.rb +227 -0
- data/lib/easy_ml/features.rb +61 -0
- data/lib/easy_ml/initializers/inflections.rb +17 -3
- data/lib/easy_ml/logging.rb +2 -2
- data/lib/easy_ml/predict.rb +74 -0
- data/lib/easy_ml/railtie/generators/migration/migration_generator.rb +192 -36
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_column_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_columns.rb.tt +25 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_dataset_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasets.rb.tt +31 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasource_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_datasources.rb.tt +16 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_deploys.rb.tt +24 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_events.rb.tt +20 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_feature_histories.rb.tt +14 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_features.rb.tt +32 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_file_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_files.rb.tt +17 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_model_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_models.rb.tt +20 -9
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_predictions.rb.tt +17 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_retraining_jobs.rb.tt +77 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_settings.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_splitter_histories.rb.tt +9 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_splitters.rb.tt +15 -0
- data/lib/easy_ml/railtie/templates/migration/create_easy_ml_tuner_jobs.rb.tt +40 -0
- data/lib/easy_ml/support/est.rb +5 -1
- data/lib/easy_ml/support/file_rotate.rb +79 -15
- data/lib/easy_ml/support/file_support.rb +9 -0
- data/lib/easy_ml/support/local_file.rb +24 -0
- data/lib/easy_ml/support/lockable.rb +62 -0
- data/lib/easy_ml/support/synced_file.rb +103 -0
- data/lib/easy_ml/support/utc.rb +5 -1
- data/lib/easy_ml/support.rb +6 -3
- data/lib/easy_ml/version.rb +4 -1
- data/lib/easy_ml.rb +7 -2
- metadata +355 -72
- data/app/models/easy_ml/models.rb +0 -5
- data/lib/easy_ml/core/model.rb +0 -30
- data/lib/easy_ml/core/model_core.rb +0 -181
- data/lib/easy_ml/core/models/hyperparameters/base.rb +0 -34
- data/lib/easy_ml/core/models/hyperparameters/xgboost.rb +0 -19
- data/lib/easy_ml/core/models/xgboost.rb +0 -10
- data/lib/easy_ml/core/models/xgboost_core.rb +0 -220
- data/lib/easy_ml/core/models.rb +0 -10
- data/lib/easy_ml/core/uploaders/model_uploader.rb +0 -24
- data/lib/easy_ml/core/uploaders.rb +0 -7
- data/lib/easy_ml/data/dataloader.rb +0 -6
- data/lib/easy_ml/data/dataset/data/preprocessor/statistics.json +0 -31
- data/lib/easy_ml/data/dataset/data/sample_info.json +0 -1
- data/lib/easy_ml/data/dataset/dataset/files/sample_info.json +0 -1
- data/lib/easy_ml/data/dataset/splits/file_split.rb +0 -140
- data/lib/easy_ml/data/dataset/splits/in_memory_split.rb +0 -49
- data/lib/easy_ml/data/dataset/splits/split.rb +0 -98
- data/lib/easy_ml/data/dataset/splits.rb +0 -11
- data/lib/easy_ml/data/dataset/splitters/date_splitter.rb +0 -43
- data/lib/easy_ml/data/dataset/splitters.rb +0 -9
- data/lib/easy_ml/data/dataset.rb +0 -430
- data/lib/easy_ml/data/datasource/datasource_factory.rb +0 -60
- data/lib/easy_ml/data/datasource/file_datasource.rb +0 -40
- data/lib/easy_ml/data/datasource/merged_datasource.rb +0 -64
- data/lib/easy_ml/data/datasource/polars_datasource.rb +0 -41
- data/lib/easy_ml/data/datasource/s3_datasource.rb +0 -89
- data/lib/easy_ml/data/datasource.rb +0 -33
- data/lib/easy_ml/data/preprocessor/preprocessor.rb +0 -205
- data/lib/easy_ml/data/preprocessor/simple_imputer.rb +0 -402
- data/lib/easy_ml/deployment.rb +0 -5
- data/lib/easy_ml/support/synced_directory.rb +0 -134
- data/lib/easy_ml/transforms.rb +0 -29
- /data/{lib/easy_ml/core → app/models/easy_ml}/models/hyperparameters.rb +0 -0
@@ -0,0 +1,726 @@
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
2
|
+
import { X, AlertCircle, Calendar, Settings2, Info, Layers, Repeat } from 'lucide-react';
|
3
|
+
import { SearchableSelect } from './SearchableSelect';
|
4
|
+
|
5
|
+
interface ScheduleModalProps {
|
6
|
+
isOpen: boolean;
|
7
|
+
onClose: () => void;
|
8
|
+
onSave: (data: any) => void;
|
9
|
+
initialData: {
|
10
|
+
task: string;
|
11
|
+
metrics: string[];
|
12
|
+
modelType?: string;
|
13
|
+
dataset?: {
|
14
|
+
columns: Array<{ name: string }>;
|
15
|
+
};
|
16
|
+
retraining_job?: {
|
17
|
+
frequency: string;
|
18
|
+
tuning_frequency: string;
|
19
|
+
at: {
|
20
|
+
hour: number;
|
21
|
+
day_of_week?: number;
|
22
|
+
day_of_month?: number;
|
23
|
+
}
|
24
|
+
direction: string;
|
25
|
+
batch_mode?: boolean;
|
26
|
+
batch_size?: number;
|
27
|
+
batch_overlap?: number;
|
28
|
+
batch_key?: string;
|
29
|
+
active: boolean;
|
30
|
+
metric?: string;
|
31
|
+
threshold?: number;
|
32
|
+
tuner_config?: {
|
33
|
+
n_trials: number;
|
34
|
+
objective: string;
|
35
|
+
config: Record<string, any>;
|
36
|
+
};
|
37
|
+
tuning_enabled?: boolean;
|
38
|
+
};
|
39
|
+
};
|
40
|
+
tunerJobConstants: any;
|
41
|
+
timezone: string;
|
42
|
+
retrainingJobConstants: any;
|
43
|
+
}
|
44
|
+
|
45
|
+
const METRICS = {
|
46
|
+
classification: [
|
47
|
+
{ value: 'accuracy_score', label: 'Accuracy', description: 'Overall prediction accuracy' },
|
48
|
+
{ value: 'precision_score', label: 'Precision', description: 'Ratio of true positives to predicted positives' },
|
49
|
+
{ value: 'recall_score', label: 'Recall', description: 'Ratio of true positives to actual positives' },
|
50
|
+
{ value: 'f1_score', label: 'F1 Score', description: 'Harmonic mean of precision and recall' }
|
51
|
+
],
|
52
|
+
regression: [
|
53
|
+
{ value: 'mean_absolute_error', label: 'Mean Absolute Error', description: 'Average absolute differences between predicted and actual values' },
|
54
|
+
{ value: 'mean_squared_error', label: 'Mean Squared Error', description: 'Average squared differences between predicted and actual values' },
|
55
|
+
{ value: 'root_mean_squared_error', label: 'Root Mean Squared Error', description: 'Square root of mean squared error' },
|
56
|
+
{ value: 'r2_score', label: 'R² Score', description: 'Proportion of variance in the target that is predictable' }
|
57
|
+
]
|
58
|
+
};
|
59
|
+
|
60
|
+
export function ScheduleModal({ isOpen, onClose, onSave, initialData, tunerJobConstants, timezone, retrainingJobConstants }: ScheduleModalProps) {
|
61
|
+
const [showBatchTrainingInfo, setShowBatchTrainingInfo] = useState(false);
|
62
|
+
const [activeBatchPopover, setActiveBatchPopover] = useState<'size' | 'overlap' | null>(null);
|
63
|
+
|
64
|
+
// Get all base parameters (those with options)
|
65
|
+
const baseParameters = Object.entries(tunerJobConstants)
|
66
|
+
.filter(([_, value]) => Array.isArray(value.options))
|
67
|
+
.reduce((acc, [key, value]) => ({
|
68
|
+
...acc,
|
69
|
+
[key]: value.options[0].value // Default to first option if not set
|
70
|
+
}), {});
|
71
|
+
|
72
|
+
// Get default numeric parameters for the default booster
|
73
|
+
const defaultBooster = baseParameters.booster;
|
74
|
+
const defaultNumericParameters = Object.entries(tunerJobConstants.hyperparameters[defaultBooster] || {})
|
75
|
+
.filter(([_, value]) => !Array.isArray(value.options))
|
76
|
+
.reduce((acc, [key, value]) => ({
|
77
|
+
...acc,
|
78
|
+
[key]: {
|
79
|
+
min: value.min,
|
80
|
+
max: value.max
|
81
|
+
}
|
82
|
+
}), {});
|
83
|
+
|
84
|
+
const [formData, setFormData] = useState({
|
85
|
+
retraining_job_attributes: {
|
86
|
+
id: initialData.retraining_job?.id || null,
|
87
|
+
active: initialData.retraining_job?.active ?? false,
|
88
|
+
frequency: initialData.retraining_job?.frequency || retrainingJobConstants.frequency[0].value as string,
|
89
|
+
tuning_frequency: initialData.retraining_job?.tuning_frequency || 'month',
|
90
|
+
direction: initialData.retraining_job?.direction || 'maximize',
|
91
|
+
batch_mode: initialData.retraining_job?.batch_mode || false,
|
92
|
+
batch_size: initialData.retraining_job?.batch_size || 100,
|
93
|
+
batch_overlap: initialData.retraining_job?.batch_overlap || 1,
|
94
|
+
batch_key: initialData.retraining_job?.batch_key || '',
|
95
|
+
at: {
|
96
|
+
hour: initialData.retraining_job?.at?.hour ?? 2,
|
97
|
+
day_of_week: initialData.retraining_job?.at?.day_of_week ?? 1,
|
98
|
+
day_of_month: initialData.retraining_job?.at?.day_of_month ?? 1
|
99
|
+
},
|
100
|
+
metric: initialData.retraining_job?.metric || METRICS[initialData.task === 'classification' ? 'classification' : 'regression'][0].value,
|
101
|
+
threshold: initialData.retraining_job?.threshold || (initialData.task === 'classification' ? 0.85 : 0.1),
|
102
|
+
tuner_config: initialData.retraining_job?.tuner_config ? {
|
103
|
+
n_trials: initialData.retraining_job.tuner_config.n_trials || 10,
|
104
|
+
config: {
|
105
|
+
...baseParameters,
|
106
|
+
...defaultNumericParameters,
|
107
|
+
...initialData.retraining_job.tuner_config.config
|
108
|
+
}
|
109
|
+
} : undefined,
|
110
|
+
tuning_enabled: initialData.retraining_job?.tuning_enabled ?? false
|
111
|
+
}
|
112
|
+
});
|
113
|
+
|
114
|
+
useEffect(() => {
|
115
|
+
if (formData.retraining_job_attributes.tuner_config && Object.keys(formData.retraining_job_attributes.tuner_config.config).length === 0) {
|
116
|
+
setFormData(prev => ({
|
117
|
+
...prev,
|
118
|
+
retraining_job_attributes: {
|
119
|
+
...prev.retraining_job_attributes,
|
120
|
+
tuner_config: {
|
121
|
+
...prev.retraining_job_attributes.tuner_config,
|
122
|
+
config: {
|
123
|
+
...baseParameters,
|
124
|
+
...defaultNumericParameters
|
125
|
+
}
|
126
|
+
}
|
127
|
+
}
|
128
|
+
}));
|
129
|
+
}
|
130
|
+
}, [formData.retraining_job_attributes.tuner_config]);
|
131
|
+
|
132
|
+
if (!isOpen) return null;
|
133
|
+
|
134
|
+
const handleBaseParameterChange = (parameter: string, value: string) => {
|
135
|
+
setFormData(prev => ({
|
136
|
+
...prev,
|
137
|
+
retraining_job_attributes: {
|
138
|
+
...prev.retraining_job_attributes,
|
139
|
+
tuner_config: {
|
140
|
+
...prev.retraining_job_attributes.tuner_config,
|
141
|
+
config: {
|
142
|
+
...prev.retraining_job_attributes.tuner_config.config,
|
143
|
+
[parameter]: value
|
144
|
+
}
|
145
|
+
}
|
146
|
+
}
|
147
|
+
}));
|
148
|
+
};
|
149
|
+
|
150
|
+
const renderHyperparameterControls = () => {
|
151
|
+
const baseParameters = Object.entries(tunerJobConstants).filter(
|
152
|
+
([key, value]) => Array.isArray(value.options)
|
153
|
+
);
|
154
|
+
|
155
|
+
// Include all base parameters, not just those in config
|
156
|
+
const selectedBaseParams = baseParameters.map(([key]) => key);
|
157
|
+
|
158
|
+
return (
|
159
|
+
<div className="space-y-4">
|
160
|
+
{baseParameters.map(([key, value]) => (
|
161
|
+
<div key={key}>
|
162
|
+
<label className="block text-sm font-medium text-gray-700">
|
163
|
+
{value.label}
|
164
|
+
</label>
|
165
|
+
<SearchableSelect
|
166
|
+
options={value.options.map((option: any) => ({
|
167
|
+
value: option.value,
|
168
|
+
label: option.label,
|
169
|
+
description: option.description
|
170
|
+
}))}
|
171
|
+
value={formData.retraining_job_attributes.tuner_config?.config[key] || value.options[0].value}
|
172
|
+
onChange={(val) => handleBaseParameterChange(key, val as string)}
|
173
|
+
/>
|
174
|
+
</div>
|
175
|
+
))}
|
176
|
+
|
177
|
+
{selectedBaseParams.map(param => {
|
178
|
+
const subParams = Object.entries(tunerJobConstants).filter(
|
179
|
+
([key, value]) => value.depends_on === param
|
180
|
+
);
|
181
|
+
|
182
|
+
const selectedValue = formData.retraining_job_attributes.tuner_config?.config[param] ||
|
183
|
+
tunerJobConstants[param].options[0].value; // Use default if not in config
|
184
|
+
|
185
|
+
return (
|
186
|
+
<div key={param} className="space-y-4">
|
187
|
+
<h4 className="text-sm font-medium text-gray-900">Parameter Ranges</h4>
|
188
|
+
<div className="space-y-4 max-h-[400px] overflow-y-auto pr-2">
|
189
|
+
{subParams.map(([subKey, subValue]: any) => {
|
190
|
+
const relevantParams = subValue[selectedValue];
|
191
|
+
if (!relevantParams) return null;
|
192
|
+
|
193
|
+
return Object.entries(relevantParams).map(([paramKey, paramValue]: any) => {
|
194
|
+
if (paramValue.min !== undefined && paramValue.max !== undefined) {
|
195
|
+
return (
|
196
|
+
<div key={paramKey} className="bg-gray-50 p-4 rounded-lg">
|
197
|
+
<div className="flex items-center justify-between mb-2">
|
198
|
+
<label className="text-sm font-medium text-gray-900">
|
199
|
+
{paramValue.label}
|
200
|
+
</label>
|
201
|
+
<span className="text-xs text-gray-500">{paramValue.description}</span>
|
202
|
+
</div>
|
203
|
+
<div className="grid grid-cols-2 gap-4">
|
204
|
+
<div>
|
205
|
+
<label className="block text-xs text-gray-500 mb-1">
|
206
|
+
Minimum
|
207
|
+
</label>
|
208
|
+
<input
|
209
|
+
type="number"
|
210
|
+
min={paramValue.min}
|
211
|
+
max={paramValue.max}
|
212
|
+
step={paramValue.step}
|
213
|
+
value={formData.retraining_job_attributes.tuner_config?.config[paramKey]?.min ?? paramValue.min}
|
214
|
+
onChange={(e) => handleParameterChange(
|
215
|
+
paramKey,
|
216
|
+
'min',
|
217
|
+
parseFloat(e.target.value)
|
218
|
+
)}
|
219
|
+
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
220
|
+
/>
|
221
|
+
</div>
|
222
|
+
<div>
|
223
|
+
<label className="block text-xs text-gray-500 mb-1">
|
224
|
+
Maximum
|
225
|
+
</label>
|
226
|
+
<input
|
227
|
+
type="number"
|
228
|
+
min={paramValue.min}
|
229
|
+
max={paramValue.max}
|
230
|
+
step={paramValue.step}
|
231
|
+
value={formData.retraining_job_attributes.tuner_config?.config[paramKey]?.max ?? paramValue.max}
|
232
|
+
onChange={(e) => handleParameterChange(
|
233
|
+
paramKey,
|
234
|
+
'max',
|
235
|
+
parseFloat(e.target.value)
|
236
|
+
)}
|
237
|
+
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500"
|
238
|
+
/>
|
239
|
+
</div>
|
240
|
+
</div>
|
241
|
+
</div>
|
242
|
+
);
|
243
|
+
}
|
244
|
+
return null;
|
245
|
+
});
|
246
|
+
})}
|
247
|
+
</div>
|
248
|
+
</div>
|
249
|
+
);
|
250
|
+
})}
|
251
|
+
</div>
|
252
|
+
);
|
253
|
+
};
|
254
|
+
|
255
|
+
const handleParameterChange = (parameter: string, type: 'min' | 'max', value: number) => {
|
256
|
+
setFormData(prev => ({
|
257
|
+
...prev,
|
258
|
+
retraining_job_attributes: {
|
259
|
+
...prev.retraining_job_attributes,
|
260
|
+
tuner_config: {
|
261
|
+
...prev.retraining_job_attributes.tuner_config,
|
262
|
+
config: {
|
263
|
+
...prev.retraining_job_attributes.tuner_config.config,
|
264
|
+
[parameter]: {
|
265
|
+
...prev.retraining_job_attributes.tuner_config.config[parameter],
|
266
|
+
[type]: value
|
267
|
+
}
|
268
|
+
}
|
269
|
+
}
|
270
|
+
}
|
271
|
+
}));
|
272
|
+
};
|
273
|
+
|
274
|
+
const handleTrainingScheduleChange = (field: string, value: string | number) => {
|
275
|
+
setFormData(prev => {
|
276
|
+
if (field === 'hour' || field === 'day_of_week' || field === 'day_of_month') {
|
277
|
+
return {
|
278
|
+
...prev,
|
279
|
+
retraining_job_attributes: {
|
280
|
+
...prev.retraining_job_attributes,
|
281
|
+
at: {
|
282
|
+
...prev.retraining_job_attributes.at,
|
283
|
+
[field]: value
|
284
|
+
}
|
285
|
+
}
|
286
|
+
};
|
287
|
+
}
|
288
|
+
return {
|
289
|
+
...prev,
|
290
|
+
retraining_job_attributes: {
|
291
|
+
...prev.retraining_job_attributes,
|
292
|
+
[field]: value
|
293
|
+
}
|
294
|
+
};
|
295
|
+
});
|
296
|
+
};
|
297
|
+
|
298
|
+
const handleEvaluatorChange = (field: string, value: string | number) => {
|
299
|
+
setFormData(prev => ({
|
300
|
+
...prev,
|
301
|
+
retraining_job_attributes: {
|
302
|
+
...prev.retraining_job_attributes,
|
303
|
+
[field]: value
|
304
|
+
}
|
305
|
+
}));
|
306
|
+
};
|
307
|
+
|
308
|
+
const handleSave = () => {
|
309
|
+
const { retraining_job_attributes } = formData;
|
310
|
+
const at: any = { hour: retraining_job_attributes.at.hour };
|
311
|
+
|
312
|
+
// Only include relevant date attributes based on frequency
|
313
|
+
switch (retraining_job_attributes.frequency) {
|
314
|
+
case 'day':
|
315
|
+
// For daily frequency, only include hour
|
316
|
+
break;
|
317
|
+
case 'week':
|
318
|
+
// For weekly frequency, include hour and day_of_week
|
319
|
+
at.day_of_week = retraining_job_attributes.at.day_of_week;
|
320
|
+
break;
|
321
|
+
case 'month':
|
322
|
+
// For monthly frequency, include hour and day_of_month
|
323
|
+
at.day_of_month = retraining_job_attributes.at.day_of_month;
|
324
|
+
break;
|
325
|
+
}
|
326
|
+
|
327
|
+
const updatedData = {
|
328
|
+
retraining_job_attributes: {
|
329
|
+
...retraining_job_attributes,
|
330
|
+
at
|
331
|
+
}
|
332
|
+
};
|
333
|
+
|
334
|
+
onSave(updatedData);
|
335
|
+
onClose();
|
336
|
+
};
|
337
|
+
|
338
|
+
return (
|
339
|
+
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
|
340
|
+
<div className="bg-white rounded-lg w-full max-w-6xl max-h-[90vh] overflow-hidden">
|
341
|
+
<div className="flex justify-between items-center p-4 border-b">
|
342
|
+
<h2 className="text-lg font-semibold">Training Configuration</h2>
|
343
|
+
<button
|
344
|
+
onClick={onClose}
|
345
|
+
className="text-gray-500 hover:text-gray-700"
|
346
|
+
>
|
347
|
+
<X className="w-5 h-5" />
|
348
|
+
</button>
|
349
|
+
</div>
|
350
|
+
|
351
|
+
<div className="p-6 grid grid-cols-2 gap-8 max-h-[calc(90vh-8rem)] overflow-y-auto">
|
352
|
+
{/* Left Column */}
|
353
|
+
<div className="space-y-8">
|
354
|
+
{/* Training Schedule */}
|
355
|
+
<div className="space-y-4">
|
356
|
+
<div className="flex items-center justify-between">
|
357
|
+
<div className="flex items-center gap-2">
|
358
|
+
<Calendar className="w-5 h-5 text-blue-600" />
|
359
|
+
<h3 className="text-lg font-medium text-gray-900">Training Schedule</h3>
|
360
|
+
</div>
|
361
|
+
<div className="flex items-center">
|
362
|
+
<input
|
363
|
+
type="checkbox"
|
364
|
+
id="scheduleEnabled"
|
365
|
+
checked={formData.retraining_job_attributes.active}
|
366
|
+
onChange={(e) => setFormData(prev => ({
|
367
|
+
...prev,
|
368
|
+
retraining_job_attributes: {
|
369
|
+
...prev.retraining_job_attributes,
|
370
|
+
active: e.target.checked
|
371
|
+
}
|
372
|
+
}))}
|
373
|
+
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
374
|
+
/>
|
375
|
+
<label htmlFor="scheduleEnabled" className="ml-2 text-sm text-gray-700">
|
376
|
+
Enable scheduled training
|
377
|
+
</label>
|
378
|
+
</div>
|
379
|
+
</div>
|
380
|
+
|
381
|
+
{!formData.retraining_job_attributes.active && (
|
382
|
+
<div className="bg-gray-50 rounded-lg p-4">
|
383
|
+
<div className="flex items-start gap-2">
|
384
|
+
<AlertCircle className="w-5 h-5 text-gray-400 mt-0.5" />
|
385
|
+
<div>
|
386
|
+
<h4 className="text-sm font-medium text-gray-900">Manual Training Mode</h4>
|
387
|
+
<p className="mt-1 text-sm text-gray-500">
|
388
|
+
The model will only be trained when you manually trigger training. You can do this from the model details page at any time.
|
389
|
+
</p>
|
390
|
+
</div>
|
391
|
+
</div>
|
392
|
+
</div>
|
393
|
+
)}
|
394
|
+
|
395
|
+
{formData.retraining_job_attributes.active && (
|
396
|
+
<>
|
397
|
+
<div className="space-y-6">
|
398
|
+
<div className="grid grid-cols-2 gap-4">
|
399
|
+
<div>
|
400
|
+
<label className="block text-sm font-medium text-gray-700">
|
401
|
+
Frequency
|
402
|
+
</label>
|
403
|
+
<SearchableSelect
|
404
|
+
options={retrainingJobConstants.frequency.map((freq: { value: string; label: string; description: string }) => ({
|
405
|
+
value: freq.value,
|
406
|
+
label: freq.label,
|
407
|
+
description: freq.description
|
408
|
+
}))}
|
409
|
+
value={formData.retraining_job_attributes.frequency}
|
410
|
+
onChange={(value) => handleTrainingScheduleChange('frequency', value)}
|
411
|
+
/>
|
412
|
+
</div>
|
413
|
+
|
414
|
+
{formData.retraining_job_attributes.frequency === 'week' && (
|
415
|
+
<div>
|
416
|
+
<label className="block text-sm font-medium text-gray-700">
|
417
|
+
Day of Week
|
418
|
+
</label>
|
419
|
+
<SearchableSelect
|
420
|
+
options={[
|
421
|
+
{ value: 0, label: 'Sunday' },
|
422
|
+
{ value: 1, label: 'Monday' },
|
423
|
+
{ value: 2, label: 'Tuesday' },
|
424
|
+
{ value: 3, label: 'Wednesday' },
|
425
|
+
{ value: 4, label: 'Thursday' },
|
426
|
+
{ value: 5, label: 'Friday' },
|
427
|
+
{ value: 6, label: 'Saturday' }
|
428
|
+
]}
|
429
|
+
value={formData.retraining_job_attributes.at.day_of_week}
|
430
|
+
onChange={(value) => handleTrainingScheduleChange('day_of_week', value)}
|
431
|
+
/>
|
432
|
+
</div>
|
433
|
+
)}
|
434
|
+
|
435
|
+
{formData.retraining_job_attributes.frequency === 'month' && (
|
436
|
+
<div>
|
437
|
+
<label className="block text-sm font-medium text-gray-700">
|
438
|
+
Day of Month
|
439
|
+
</label>
|
440
|
+
<SearchableSelect
|
441
|
+
options={Array.from({ length: 31 }, (_, i) => ({
|
442
|
+
value: i + 1,
|
443
|
+
label: `Day ${i + 1}`
|
444
|
+
}))}
|
445
|
+
value={formData.retraining_job_attributes.at.day_of_month}
|
446
|
+
onChange={(value) => handleTrainingScheduleChange('day_of_month', value)}
|
447
|
+
/>
|
448
|
+
</div>
|
449
|
+
)}
|
450
|
+
|
451
|
+
<div>
|
452
|
+
<label className="block text-sm font-medium text-gray-700">
|
453
|
+
Hour ({timezone})
|
454
|
+
</label>
|
455
|
+
<SearchableSelect
|
456
|
+
options={Array.from({ length: 24 }, (_, i) => ({
|
457
|
+
value: i,
|
458
|
+
label: `${i}:00`
|
459
|
+
}))}
|
460
|
+
value={formData.retraining_job_attributes.at.hour}
|
461
|
+
onChange={(value) => handleTrainingScheduleChange('hour', value)}
|
462
|
+
/>
|
463
|
+
</div>
|
464
|
+
</div>
|
465
|
+
</div>
|
466
|
+
</>
|
467
|
+
)}
|
468
|
+
|
469
|
+
{/* Batch Training Options */}
|
470
|
+
<div className="space-y-4 pt-4 border-t">
|
471
|
+
<div className="flex items-center justify-between">
|
472
|
+
<div className="flex items-center gap-2">
|
473
|
+
<label htmlFor="batchTrainingEnabled" className="text-sm font-medium text-gray-700 flex items-center gap-2">
|
474
|
+
Enable Batch Training
|
475
|
+
<button
|
476
|
+
type="button"
|
477
|
+
onClick={() => setShowBatchTrainingInfo(!showBatchTrainingInfo)}
|
478
|
+
className="text-gray-400 hover:text-gray-600"
|
479
|
+
>
|
480
|
+
<Info className="w-4 h-4" />
|
481
|
+
</button>
|
482
|
+
</label>
|
483
|
+
</div>
|
484
|
+
<input
|
485
|
+
type="checkbox"
|
486
|
+
id="batchMode"
|
487
|
+
checked={formData.retraining_job_attributes.batch_mode}
|
488
|
+
onChange={(e) => setFormData({ ...formData, retraining_job_attributes: { ...formData.retraining_job_attributes, batch_mode: e.target.checked } })}
|
489
|
+
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
490
|
+
/>
|
491
|
+
</div>
|
492
|
+
|
493
|
+
{showBatchTrainingInfo && (
|
494
|
+
<div className="bg-blue-50 rounded-lg p-4 text-sm text-blue-700">
|
495
|
+
<ul className="space-y-2">
|
496
|
+
<li>• When disabled, the model will train on the entire dataset in a single pass.</li>
|
497
|
+
<li>• When enabled, the model will learn from small batches of data iteratively, improving training speed</li>
|
498
|
+
</ul>
|
499
|
+
</div>
|
500
|
+
)}
|
501
|
+
|
502
|
+
{formData.retraining_job_attributes.batch_mode && (
|
503
|
+
<div className="mt-4">
|
504
|
+
<div className="flex items-center gap-2">
|
505
|
+
<div className="flex-1">
|
506
|
+
<label className="block text-sm font-medium text-gray-700">
|
507
|
+
Batch Size
|
508
|
+
</label>
|
509
|
+
<input
|
510
|
+
type="number"
|
511
|
+
value={formData.retraining_job_attributes.batch_size}
|
512
|
+
onChange={(e) => setFormData({
|
513
|
+
...formData,
|
514
|
+
retraining_job_attributes: {
|
515
|
+
...formData.retraining_job_attributes,
|
516
|
+
batch_size: parseInt(e.target.value)
|
517
|
+
}
|
518
|
+
})}
|
519
|
+
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
520
|
+
/>
|
521
|
+
</div>
|
522
|
+
<div className="flex-1">
|
523
|
+
<label className="block text-sm font-medium text-gray-700">
|
524
|
+
Batch Overlap
|
525
|
+
</label>
|
526
|
+
<input
|
527
|
+
type="number"
|
528
|
+
value={formData.retraining_job_attributes.batch_overlap}
|
529
|
+
onChange={(e) => setFormData({
|
530
|
+
...formData,
|
531
|
+
retraining_job_attributes: {
|
532
|
+
...formData.retraining_job_attributes,
|
533
|
+
batch_overlap: parseInt(e.target.value)
|
534
|
+
}
|
535
|
+
})}
|
536
|
+
className="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
537
|
+
/>
|
538
|
+
</div>
|
539
|
+
</div>
|
540
|
+
<div className="mt-4">
|
541
|
+
<label className="block text-sm font-medium text-gray-700">
|
542
|
+
Batch Key
|
543
|
+
</label>
|
544
|
+
<SearchableSelect
|
545
|
+
value={formData.retraining_job_attributes.batch_key}
|
546
|
+
onChange={(value) => setFormData({
|
547
|
+
...formData,
|
548
|
+
retraining_job_attributes: {
|
549
|
+
...formData.retraining_job_attributes,
|
550
|
+
batch_key: value
|
551
|
+
}
|
552
|
+
})}
|
553
|
+
options={initialData.dataset?.columns?.map(column => ({
|
554
|
+
value: column.name,
|
555
|
+
label: column.name
|
556
|
+
})) || []}
|
557
|
+
placeholder="Select a column for batch key"
|
558
|
+
/>
|
559
|
+
</div>
|
560
|
+
</div>
|
561
|
+
)}
|
562
|
+
</div>
|
563
|
+
|
564
|
+
{/* Evaluator Configuration */}
|
565
|
+
<div className="border-t border-gray-200 pt-6">
|
566
|
+
<div className="flex items-center gap-2 mb-4">
|
567
|
+
<AlertCircle className="w-5 h-5 text-blue-600" />
|
568
|
+
<h3 className="text-lg font-medium text-gray-900">Evaluator Configuration</h3>
|
569
|
+
</div>
|
570
|
+
|
571
|
+
<div className="space-y-6">
|
572
|
+
<div className="grid grid-cols-2 gap-4">
|
573
|
+
<div>
|
574
|
+
<label className="block text-sm font-medium text-gray-700">
|
575
|
+
Metric
|
576
|
+
</label>
|
577
|
+
<SearchableSelect
|
578
|
+
options={METRICS[initialData.task === 'classification' ? 'classification' : 'regression'].map((metric) => ({
|
579
|
+
value: metric.value,
|
580
|
+
label: metric.label,
|
581
|
+
description: metric.description
|
582
|
+
}))}
|
583
|
+
value={formData.retraining_job_attributes.metric}
|
584
|
+
onChange={(value) => handleEvaluatorChange('metric', value)}
|
585
|
+
/>
|
586
|
+
</div>
|
587
|
+
|
588
|
+
<div>
|
589
|
+
<label className="block text-sm font-medium text-gray-700">
|
590
|
+
Threshold
|
591
|
+
</label>
|
592
|
+
<input
|
593
|
+
type="number"
|
594
|
+
value={formData.retraining_job_attributes.threshold}
|
595
|
+
onChange={(e) => handleEvaluatorChange('threshold', parseFloat(e.target.value))}
|
596
|
+
step={0.01}
|
597
|
+
min={0}
|
598
|
+
max={1}
|
599
|
+
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-2 px-4 shadow-sm border-gray-300 border"
|
600
|
+
/>
|
601
|
+
</div>
|
602
|
+
|
603
|
+
</div>
|
604
|
+
|
605
|
+
{/* Deployment Criteria */}
|
606
|
+
<div className="bg-blue-50 rounded-md p-4">
|
607
|
+
<div className="flex items-start">
|
608
|
+
<AlertCircle className="w-5 h-5 text-blue-400 mt-0.5" />
|
609
|
+
<div className="ml-3">
|
610
|
+
<h3 className="text-sm font-medium text-blue-800">Deployment Criteria</h3>
|
611
|
+
<p className="mt-2 text-sm text-blue-700">
|
612
|
+
The model will be automatically deployed when the {formData.retraining_job_attributes.metric} is{' '}
|
613
|
+
{formData.retraining_job_attributes.direction === 'minimize' ? 'below' : 'above'} {formData.retraining_job_attributes.threshold}.
|
614
|
+
</p>
|
615
|
+
</div>
|
616
|
+
</div>
|
617
|
+
</div>
|
618
|
+
</div>
|
619
|
+
</div>
|
620
|
+
</div>
|
621
|
+
</div>
|
622
|
+
|
623
|
+
{/* Right Column */}
|
624
|
+
<div className="space-y-8">
|
625
|
+
<div className="space-y-4">
|
626
|
+
<div className="flex items-center justify-between">
|
627
|
+
<div className="flex items-center gap-2">
|
628
|
+
<Settings2 className="w-5 h-5 text-blue-600" />
|
629
|
+
<h3 className="text-lg font-medium text-gray-900">Hyperparameter Tuning</h3>
|
630
|
+
</div>
|
631
|
+
<div className="flex items-center">
|
632
|
+
<input
|
633
|
+
type="checkbox"
|
634
|
+
id="tuningEnabled"
|
635
|
+
checked={formData.retraining_job_attributes.tuning_enabled || false}
|
636
|
+
onChange={(e) => setFormData(prev => ({
|
637
|
+
...prev,
|
638
|
+
retraining_job_attributes: {
|
639
|
+
...prev.retraining_job_attributes,
|
640
|
+
tuning_enabled: e.target.checked,
|
641
|
+
tuner_config: e.target.checked ? {
|
642
|
+
n_trials: 10,
|
643
|
+
config: defaultNumericParameters
|
644
|
+
} : undefined
|
645
|
+
}
|
646
|
+
}))}
|
647
|
+
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
648
|
+
/>
|
649
|
+
<label htmlFor="tuningEnabled" className="ml-2 text-sm text-gray-700">
|
650
|
+
Enable tuning
|
651
|
+
</label>
|
652
|
+
</div>
|
653
|
+
</div>
|
654
|
+
|
655
|
+
{formData.retraining_job_attributes.tuning_enabled && (
|
656
|
+
<div className="space-y-6">
|
657
|
+
<div className="grid grid-cols-2 gap-4">
|
658
|
+
<div>
|
659
|
+
<label className="block text-sm font-medium text-gray-700">
|
660
|
+
Frequency
|
661
|
+
</label>
|
662
|
+
<SearchableSelect
|
663
|
+
options={[
|
664
|
+
{ value: 'always', label: 'Always', description: 'Tune hyperparameters every time' },
|
665
|
+
{ value: 'week', label: 'Weekly', description: 'Tune hyperparameters once every week' },
|
666
|
+
{ value: 'month', label: 'Monthly', description: 'Tune hyperparameters once every month' }
|
667
|
+
]}
|
668
|
+
value={formData.retraining_job_attributes.tuning_frequency || 'week'}
|
669
|
+
onChange={(value) => setFormData(prev => ({
|
670
|
+
...prev,
|
671
|
+
retraining_job_attributes: {
|
672
|
+
...prev.retraining_job_attributes,
|
673
|
+
tuning_frequency: value as 'week' | 'month' | 'always'
|
674
|
+
}
|
675
|
+
}))}
|
676
|
+
/>
|
677
|
+
</div>
|
678
|
+
|
679
|
+
<div>
|
680
|
+
<label className="block text-sm font-medium text-gray-700">
|
681
|
+
Number of Trials
|
682
|
+
</label>
|
683
|
+
<input
|
684
|
+
type="number"
|
685
|
+
min="1"
|
686
|
+
max="1000"
|
687
|
+
value={formData.retraining_job_attributes.tuner_config?.n_trials || 10}
|
688
|
+
onChange={(e) => setFormData(prev => ({
|
689
|
+
...prev,
|
690
|
+
retraining_job_attributes: {
|
691
|
+
...prev.retraining_job_attributes,
|
692
|
+
tuner_config: {
|
693
|
+
...prev.retraining_job_attributes.tuner_config,
|
694
|
+
n_trials: parseInt(e.target.value)
|
695
|
+
}
|
696
|
+
}
|
697
|
+
}))}
|
698
|
+
className="block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500 py-2 px-4 shadow-sm border-gray-300 border"
|
699
|
+
/>
|
700
|
+
</div>
|
701
|
+
</div>
|
702
|
+
{renderHyperparameterControls()}
|
703
|
+
</div>
|
704
|
+
)}
|
705
|
+
</div>
|
706
|
+
</div>
|
707
|
+
</div>
|
708
|
+
|
709
|
+
<div className="flex justify-end gap-4 p-4 border-t">
|
710
|
+
<button
|
711
|
+
onClick={onClose}
|
712
|
+
className="px-4 py-2 text-sm font-medium text-gray-700 hover:text-gray-500"
|
713
|
+
>
|
714
|
+
Cancel
|
715
|
+
</button>
|
716
|
+
<button
|
717
|
+
onClick={handleSave}
|
718
|
+
className="px-4 py-2 text-sm font-medium text-white bg-blue-600 hover:bg-blue-700 rounded-md"
|
719
|
+
>
|
720
|
+
Save Changes
|
721
|
+
</button>
|
722
|
+
</div>
|
723
|
+
</div>
|
724
|
+
</div>
|
725
|
+
);
|
726
|
+
}
|